From 8161c7ddcec434dbee511437ea2b1f15dd8c2b1c Mon Sep 17 00:00:00 2001 From: dmavrommatis Date: Sat, 4 Sep 2021 16:52:47 +0300 Subject: [PATCH 1/5] Change EDNS_EXPIRE field to support zero length option data (Resolves #1292) As per [RFC7134](https://datatracker.ietf.org/doc/html/rfc7314#section-2) the Expire Option in queries should be zero-length. In the current implementation the field is uint32 which always instatiates 4bytes for that field when packing to wire format. For that reason we change the field to []uint8 so it can support 0-length and 4-byte length option data. --- edns.go | 33 ++++++++++++++++++--------------- 1 file changed, 18 insertions(+), 15 deletions(-) diff --git a/edns.go b/edns.go index c9181783d..ec59fac2c 100644 --- a/edns.go +++ b/edns.go @@ -575,33 +575,36 @@ func (e *EDNS0_N3U) copy() EDNS0 { return &EDNS0_N3U{e.Code, e.AlgCode} } // EDNS0_EXPIRE implements the EDNS0 option as described in RFC 7314. type EDNS0_EXPIRE struct { - Code uint16 // Always EDNS0EXPIRE - Expire uint32 + Code uint16 // Always EDNS0EXPIRE + Expire []uint8 // can be zero or 4 length } // Option implements the EDNS0 interface. func (e *EDNS0_EXPIRE) Option() uint16 { return EDNS0EXPIRE } -func (e *EDNS0_EXPIRE) String() string { return strconv.FormatUint(uint64(e.Expire), 10) } func (e *EDNS0_EXPIRE) copy() EDNS0 { return &EDNS0_EXPIRE{e.Code, e.Expire} } - func (e *EDNS0_EXPIRE) pack() ([]byte, error) { - b := make([]byte, 4) - binary.BigEndian.PutUint32(b, e.Expire) - return b, nil + if len(e.Expire) != 0 && len(e.Expire) != 4 { + return nil, errors.New("dns: expire length is not 0/4") + } + return e.Expire, nil } - func (e *EDNS0_EXPIRE) unpack(b []byte) error { - if len(b) == 0 { - // zero-length EXPIRE query, see RFC 7314 Section 2 - return nil + if len(b) != 0 && len(b) != 4 { + return errors.New("dns: expire length mismatch, want 0/4 but got " + strconv.Itoa(len(b))) } - if len(b) < 4 { - return ErrBuf - } - e.Expire = binary.BigEndian.Uint32(b) + e.Expire = b return nil } +func (e *EDNS0_EXPIRE) String() (s string) { + if len(e.Expire) == 0 { + s = "" + } else { + s = fmt.Sprintf("<%d>", binary.BigEndian.Uint32(e.Expire)) + } + return s +} + // The EDNS0_LOCAL option is used for local/experimental purposes. The option // code is recommended to be within the range [EDNS0LOCALSTART, EDNS0LOCALEND] // (RFC6891), although any unassigned code can actually be used. The content of From 6f14f3c3f399f7a4e74712672544d3b22f05d2cb Mon Sep 17 00:00:00 2001 From: dmavrommatis Date: Tue, 14 Sep 2021 17:45:20 +0200 Subject: [PATCH 2/5] addressed comments --- edns.go | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/edns.go b/edns.go index ec59fac2c..89dc6b8f1 100644 --- a/edns.go +++ b/edns.go @@ -576,7 +576,7 @@ func (e *EDNS0_N3U) copy() EDNS0 { return &EDNS0_N3U{e.Code, e.AlgCode} } // EDNS0_EXPIRE implements the EDNS0 option as described in RFC 7314. type EDNS0_EXPIRE struct { Code uint16 // Always EDNS0EXPIRE - Expire []uint8 // can be zero or 4 length + Expire []uint8 // either zero or 4 octets } // Option implements the EDNS0 interface. @@ -590,7 +590,7 @@ func (e *EDNS0_EXPIRE) pack() ([]byte, error) { } func (e *EDNS0_EXPIRE) unpack(b []byte) error { if len(b) != 0 && len(b) != 4 { - return errors.New("dns: expire length mismatch, want 0/4 but got " + strconv.Itoa(len(b))) + return ErrBuf } e.Expire = b return nil @@ -598,11 +598,9 @@ func (e *EDNS0_EXPIRE) unpack(b []byte) error { func (e *EDNS0_EXPIRE) String() (s string) { if len(e.Expire) == 0 { - s = "" - } else { - s = fmt.Sprintf("<%d>", binary.BigEndian.Uint32(e.Expire)) + return "" } - return s + return fmt.Sprintf("%d", binary.BigEndian.Uint32(e.Expire)) } // The EDNS0_LOCAL option is used for local/experimental purposes. The option From ba458781c0c660ecaff1312d22cf75213ed19ec9 Mon Sep 17 00:00:00 2001 From: dmavrommatis Date: Fri, 29 Oct 2021 01:00:33 +0200 Subject: [PATCH 3/5] addressed comments --- edns.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/edns.go b/edns.go index 89dc6b8f1..eac512f79 100644 --- a/edns.go +++ b/edns.go @@ -598,9 +598,9 @@ func (e *EDNS0_EXPIRE) unpack(b []byte) error { func (e *EDNS0_EXPIRE) String() (s string) { if len(e.Expire) == 0 { - return "" + return "" } - return fmt.Sprintf("%d", binary.BigEndian.Uint32(e.Expire)) + return strconv.FormatUint(binary.BigEndian.Uint64(e.Expire), 10) } // The EDNS0_LOCAL option is used for local/experimental purposes. The option From dc39a2cf2a55a22f6ef5779ec6f06c160f019c97 Mon Sep 17 00:00:00 2001 From: dmavrommatis Date: Thu, 4 Nov 2021 19:24:07 +0100 Subject: [PATCH 4/5] make change backwards compatible --- edns.go | 31 +++++++++++++++++++++---------- 1 file changed, 21 insertions(+), 10 deletions(-) diff --git a/edns.go b/edns.go index eac512f79..8f91d5056 100644 --- a/edns.go +++ b/edns.go @@ -575,32 +575,43 @@ func (e *EDNS0_N3U) copy() EDNS0 { return &EDNS0_N3U{e.Code, e.AlgCode} } // EDNS0_EXPIRE implements the EDNS0 option as described in RFC 7314. type EDNS0_EXPIRE struct { - Code uint16 // Always EDNS0EXPIRE - Expire []uint8 // either zero or 4 octets + Code uint16 // Always EDNS0EXPIRE + Expire uint32 + Empty bool } // Option implements the EDNS0 interface. func (e *EDNS0_EXPIRE) Option() uint16 { return EDNS0EXPIRE } -func (e *EDNS0_EXPIRE) copy() EDNS0 { return &EDNS0_EXPIRE{e.Code, e.Expire} } +func (e *EDNS0_EXPIRE) copy() EDNS0 { return &EDNS0_EXPIRE{e.Code, e.Expire, e.Empty} } + func (e *EDNS0_EXPIRE) pack() ([]byte, error) { - if len(e.Expire) != 0 && len(e.Expire) != 4 { - return nil, errors.New("dns: expire length is not 0/4") + if e.Empty { + return []byte{}, nil } - return e.Expire, nil + b := make([]byte, 4) + binary.BigEndian.PutUint32(b, e.Expire) + return b, nil } + func (e *EDNS0_EXPIRE) unpack(b []byte) error { - if len(b) != 0 && len(b) != 4 { + if len(b) == 0 { + // zero-length EXPIRE query, see RFC 7314 Section 2 + e.Empty = true + return nil + } + if len(b) < 4 { return ErrBuf } - e.Expire = b + e.Expire = binary.BigEndian.Uint32(b) + e.Empty = false return nil } func (e *EDNS0_EXPIRE) String() (s string) { - if len(e.Expire) == 0 { + if e.Empty { return "" } - return strconv.FormatUint(binary.BigEndian.Uint64(e.Expire), 10) + return strconv.FormatUint(uint64(e.Expire), 10) } // The EDNS0_LOCAL option is used for local/experimental purposes. The option From 37bd514b26f83711526c53e0fc792de70b216a31 Mon Sep 17 00:00:00 2001 From: dmavrommatis Date: Mon, 7 Mar 2022 13:11:29 +0100 Subject: [PATCH 5/5] add comment for Empty field --- edns.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/edns.go b/edns.go index 8f91d5056..4bb3ecb0f 100644 --- a/edns.go +++ b/edns.go @@ -577,7 +577,7 @@ func (e *EDNS0_N3U) copy() EDNS0 { return &EDNS0_N3U{e.Code, e.AlgCode} } type EDNS0_EXPIRE struct { Code uint16 // Always EDNS0EXPIRE Expire uint32 - Empty bool + Empty bool // Empty is used to signal an empty Expire option in a backwards compatible way, it's not used on the wire. } // Option implements the EDNS0 interface.