vendor: update all dependencies
* Update all dependencies * Remove all `[[constraint]]` from Gopkg.toml * Add in the minimum number of `[[override]]` to build * Remove go get of github.com/inconshreveable/mousetrap as it is vendored * Update docs with new policy on constraints
This commit is contained in:
+144
@@ -5,6 +5,9 @@
|
||||
// Package dnsmessage provides a mostly RFC 1035 compliant implementation of
|
||||
// DNS message packing and unpacking.
|
||||
//
|
||||
// The package also supports messages with Extension Mechanisms for DNS
|
||||
// (EDNS(0)) as defined in RFC 6891.
|
||||
//
|
||||
// This implementation is designed to minimize heap allocations and avoid
|
||||
// unnecessary packing and unpacking as much as possible.
|
||||
package dnsmessage
|
||||
@@ -39,6 +42,7 @@ const (
|
||||
TypeTXT Type = 16
|
||||
TypeAAAA Type = 28
|
||||
TypeSRV Type = 33
|
||||
TypeOPT Type = 41
|
||||
|
||||
// Question.Type
|
||||
TypeWKS Type = 11
|
||||
@@ -802,6 +806,24 @@ func (p *Parser) AAAAResource() (AAAAResource, error) {
|
||||
return r, nil
|
||||
}
|
||||
|
||||
// OPTResource parses a single OPTResource.
|
||||
//
|
||||
// One of the XXXHeader methods must have been called before calling this
|
||||
// method.
|
||||
func (p *Parser) OPTResource() (OPTResource, error) {
|
||||
if !p.resHeaderValid || p.resHeader.Type != TypeOPT {
|
||||
return OPTResource{}, ErrNotStarted
|
||||
}
|
||||
r, err := unpackOPTResource(p.msg, p.off, p.resHeader.Length)
|
||||
if err != nil {
|
||||
return OPTResource{}, err
|
||||
}
|
||||
p.off += int(p.resHeader.Length)
|
||||
p.resHeaderValid = false
|
||||
p.index++
|
||||
return r, nil
|
||||
}
|
||||
|
||||
// Unpack parses a full Message.
|
||||
func (m *Message) Unpack(msg []byte) error {
|
||||
var p Parser
|
||||
@@ -1278,6 +1300,30 @@ func (b *Builder) AAAAResource(h ResourceHeader, r AAAAResource) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// OPTResource adds a single OPTResource.
|
||||
func (b *Builder) OPTResource(h ResourceHeader, r OPTResource) error {
|
||||
if err := b.checkResourceSection(); err != nil {
|
||||
return err
|
||||
}
|
||||
h.Type = r.realType()
|
||||
msg, length, err := h.pack(b.msg, b.compression, b.start)
|
||||
if err != nil {
|
||||
return &nestedError{"ResourceHeader", err}
|
||||
}
|
||||
preLen := len(msg)
|
||||
if msg, err = r.pack(msg, b.compression, b.start); err != nil {
|
||||
return &nestedError{"OPTResource body", err}
|
||||
}
|
||||
if err := h.fixLen(msg, length, preLen); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := b.incrementSectionCount(); err != nil {
|
||||
return err
|
||||
}
|
||||
b.msg = msg
|
||||
return nil
|
||||
}
|
||||
|
||||
// Finish ends message building and generates a binary message.
|
||||
func (b *Builder) Finish() ([]byte, error) {
|
||||
if b.section < sectionHeader {
|
||||
@@ -1366,6 +1412,44 @@ func (h *ResourceHeader) fixLen(msg []byte, length []byte, preLen int) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// EDNS(0) wire costants.
|
||||
const (
|
||||
edns0Version = 0
|
||||
|
||||
edns0DNSSECOK = 0x00008000
|
||||
ednsVersionMask = 0x00ff0000
|
||||
edns0DNSSECOKMask = 0x00ff8000
|
||||
)
|
||||
|
||||
// SetEDNS0 configures h for EDNS(0).
|
||||
//
|
||||
// The provided extRCode must be an extedned RCode.
|
||||
func (h *ResourceHeader) SetEDNS0(udpPayloadLen int, extRCode RCode, dnssecOK bool) error {
|
||||
h.Name = Name{Data: [nameLen]byte{'.'}, Length: 1} // RFC 6891 section 6.1.2
|
||||
h.Type = TypeOPT
|
||||
h.Class = Class(udpPayloadLen)
|
||||
h.TTL = uint32(extRCode) >> 4 << 24
|
||||
if dnssecOK {
|
||||
h.TTL |= edns0DNSSECOK
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// DNSSECAllowed reports whether the DNSSEC OK bit is set.
|
||||
func (h *ResourceHeader) DNSSECAllowed() bool {
|
||||
return h.TTL&edns0DNSSECOKMask == edns0DNSSECOK // RFC 6891 section 6.1.3
|
||||
}
|
||||
|
||||
// ExtendedRCode returns an extended RCode.
|
||||
//
|
||||
// The provided rcode must be the RCode in DNS message header.
|
||||
func (h *ResourceHeader) ExtendedRCode(rcode RCode) RCode {
|
||||
if h.TTL&ednsVersionMask == edns0Version { // RFC 6891 section 6.1.3
|
||||
return RCode(h.TTL>>24<<4) | rcode
|
||||
}
|
||||
return rcode
|
||||
}
|
||||
|
||||
func skipResource(msg []byte, off int) (int, error) {
|
||||
newOff, err := skipName(msg, off)
|
||||
if err != nil {
|
||||
@@ -1794,6 +1878,11 @@ func unpackResourceBody(msg []byte, off int, hdr ResourceHeader) (ResourceBody,
|
||||
rb, err = unpackSRVResource(msg, off)
|
||||
r = &rb
|
||||
name = "SRV"
|
||||
case TypeOPT:
|
||||
var rb OPTResource
|
||||
rb, err = unpackOPTResource(msg, off, hdr.Length)
|
||||
r = &rb
|
||||
name = "OPT"
|
||||
}
|
||||
if err != nil {
|
||||
return nil, off, &nestedError{name + " record", err}
|
||||
@@ -2101,3 +2190,58 @@ func unpackAAAAResource(msg []byte, off int) (AAAAResource, error) {
|
||||
}
|
||||
return AAAAResource{aaaa}, nil
|
||||
}
|
||||
|
||||
// An OPTResource is an OPT pseudo Resource record.
|
||||
//
|
||||
// The pseudo resource record is part of the extension mechanisms for DNS
|
||||
// as defined in RFC 6891.
|
||||
type OPTResource struct {
|
||||
Options []Option
|
||||
}
|
||||
|
||||
// An Option represents a DNS message option within OPTResource.
|
||||
//
|
||||
// The message option is part of the extension mechanisms for DNS as
|
||||
// defined in RFC 6891.
|
||||
type Option struct {
|
||||
Code uint16 // option code
|
||||
Data []byte
|
||||
}
|
||||
|
||||
func (r *OPTResource) realType() Type {
|
||||
return TypeOPT
|
||||
}
|
||||
|
||||
func (r *OPTResource) pack(msg []byte, compression map[string]int, compressionOff int) ([]byte, error) {
|
||||
for _, opt := range r.Options {
|
||||
msg = packUint16(msg, opt.Code)
|
||||
l := uint16(len(opt.Data))
|
||||
msg = packUint16(msg, l)
|
||||
msg = packBytes(msg, opt.Data)
|
||||
}
|
||||
return msg, nil
|
||||
}
|
||||
|
||||
func unpackOPTResource(msg []byte, off int, length uint16) (OPTResource, error) {
|
||||
var opts []Option
|
||||
for oldOff := off; off < oldOff+int(length); {
|
||||
var err error
|
||||
var o Option
|
||||
o.Code, off, err = unpackUint16(msg, off)
|
||||
if err != nil {
|
||||
return OPTResource{}, &nestedError{"Code", err}
|
||||
}
|
||||
var l uint16
|
||||
l, off, err = unpackUint16(msg, off)
|
||||
if err != nil {
|
||||
return OPTResource{}, &nestedError{"Data", err}
|
||||
}
|
||||
o.Data = make([]byte, l)
|
||||
if copy(o.Data, msg[off:]) != int(l) {
|
||||
return OPTResource{}, &nestedError{"Data", errCalcLen}
|
||||
}
|
||||
off += int(l)
|
||||
opts = append(opts, o)
|
||||
}
|
||||
return OPTResource{opts}, nil
|
||||
}
|
||||
|
||||
+269
-90
@@ -20,6 +20,14 @@ func mustNewName(name string) Name {
|
||||
return n
|
||||
}
|
||||
|
||||
func mustEDNS0ResourceHeader(l int, extrc RCode, do bool) ResourceHeader {
|
||||
h := ResourceHeader{Class: ClassINET}
|
||||
if err := h.SetEDNS0(l, extrc, do); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return h
|
||||
}
|
||||
|
||||
func (m *Message) String() string {
|
||||
s := fmt.Sprintf("Message: %#v\n", &m.Header)
|
||||
if len(m.Questions) > 0 {
|
||||
@@ -65,7 +73,7 @@ func TestQuestionPackUnpack(t *testing.T) {
|
||||
}
|
||||
buf, err := want.pack(make([]byte, 1, 50), map[string]int{}, 1)
|
||||
if err != nil {
|
||||
t.Fatal("Packing failed:", err)
|
||||
t.Fatal("Question.pack() =", err)
|
||||
}
|
||||
var p Parser
|
||||
p.msg = buf
|
||||
@@ -74,13 +82,13 @@ func TestQuestionPackUnpack(t *testing.T) {
|
||||
p.off = 1
|
||||
got, err := p.Question()
|
||||
if err != nil {
|
||||
t.Fatalf("Unpacking failed: %v\n%s", err, string(buf[1:]))
|
||||
t.Fatalf("Parser{%q}.Question() = %v", string(buf[1:]), err)
|
||||
}
|
||||
if p.off != len(buf) {
|
||||
t.Errorf("Unpacked different amount than packed: got n = %d, want = %d", p.off, len(buf))
|
||||
t.Errorf("unpacked different amount than packed: got = %d, want = %d", p.off, len(buf))
|
||||
}
|
||||
if !reflect.DeepEqual(got, want) {
|
||||
t.Errorf("Got = %+v, want = %+v", got, want)
|
||||
t.Errorf("got from Parser.Question() = %+v, want = %+v", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -100,11 +108,11 @@ func TestName(t *testing.T) {
|
||||
for _, test := range tests {
|
||||
n, err := NewName(test)
|
||||
if err != nil {
|
||||
t.Errorf("Creating name for %q: %v", test, err)
|
||||
t.Errorf("NewName(%q) = %v", test, err)
|
||||
continue
|
||||
}
|
||||
if ns := n.String(); ns != test {
|
||||
t.Errorf("Got %#v.String() = %q, want = %q", n, ns, test)
|
||||
t.Errorf("got %#v.String() = %q, want = %q", n, ns, test)
|
||||
continue
|
||||
}
|
||||
}
|
||||
@@ -132,7 +140,7 @@ func TestNamePackUnpack(t *testing.T) {
|
||||
want := mustNewName(test.want)
|
||||
buf, err := in.pack(make([]byte, 0, 30), map[string]int{}, 0)
|
||||
if err != test.err {
|
||||
t.Errorf("Packing of %q: got err = %v, want err = %v", test.in, err, test.err)
|
||||
t.Errorf("got %q.pack() = %v, want = %v", test.in, err, test.err)
|
||||
continue
|
||||
}
|
||||
if test.err != nil {
|
||||
@@ -141,19 +149,19 @@ func TestNamePackUnpack(t *testing.T) {
|
||||
var got Name
|
||||
n, err := got.unpack(buf, 0)
|
||||
if err != nil {
|
||||
t.Errorf("Unpacking for %q failed: %v", test.in, err)
|
||||
t.Errorf("%q.unpack() = %v", test.in, err)
|
||||
continue
|
||||
}
|
||||
if n != len(buf) {
|
||||
t.Errorf(
|
||||
"Unpacked different amount than packed for %q: got n = %d, want = %d",
|
||||
"unpacked different amount than packed for %q: got = %d, want = %d",
|
||||
test.in,
|
||||
n,
|
||||
len(buf),
|
||||
)
|
||||
}
|
||||
if got != want {
|
||||
t.Errorf("Unpacking packing of %q: got = %#v, want = %#v", test.in, got, want)
|
||||
t.Errorf("unpacking packing of %q: got = %#v, want = %#v", test.in, got, want)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -163,20 +171,20 @@ func TestIncompressibleName(t *testing.T) {
|
||||
compression := map[string]int{}
|
||||
buf, err := name.pack(make([]byte, 0, 100), compression, 0)
|
||||
if err != nil {
|
||||
t.Fatal("First packing failed:", err)
|
||||
t.Fatal("first Name.pack() =", err)
|
||||
}
|
||||
buf, err = name.pack(buf, compression, 0)
|
||||
if err != nil {
|
||||
t.Fatal("Second packing failed:", err)
|
||||
t.Fatal("second Name.pack() =", err)
|
||||
}
|
||||
var n1 Name
|
||||
off, err := n1.unpackCompressed(buf, 0, false /* allowCompression */)
|
||||
if err != nil {
|
||||
t.Fatal("Unpacking incompressible name without pointers failed:", err)
|
||||
t.Fatal("unpacking incompressible name without pointers failed:", err)
|
||||
}
|
||||
var n2 Name
|
||||
if _, err := n2.unpackCompressed(buf, off, false /* allowCompression */); err != errCompressedSRV {
|
||||
t.Errorf("Unpacking compressed incompressible name with pointers: got err = %v, want = %v", err, errCompressedSRV)
|
||||
t.Errorf("unpacking compressed incompressible name with pointers: got %v, want = %v", err, errCompressedSRV)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -199,7 +207,7 @@ func TestHeaderUnpackError(t *testing.T) {
|
||||
for _, want := range wants {
|
||||
n, err := h.unpack(buf, 0)
|
||||
if n != 0 || !checkErrorPrefix(err, want) {
|
||||
t.Errorf("got h.unpack([%d]byte, 0) = %d, %v, want = 0, %s", len(buf), n, err, want)
|
||||
t.Errorf("got header.unpack([%d]byte, 0) = %d, %v, want = 0, %s", len(buf), n, err, want)
|
||||
}
|
||||
buf = append(buf, 0, 0)
|
||||
}
|
||||
@@ -211,7 +219,7 @@ func TestParserStart(t *testing.T) {
|
||||
for i := 0; i <= 1; i++ {
|
||||
_, err := p.Start([]byte{})
|
||||
if !checkErrorPrefix(err, want) {
|
||||
t.Errorf("got p.Start(nil) = _, %v, want = _, %s", err, want)
|
||||
t.Errorf("got Parser.Start(nil) = _, %v, want = _, %s", err, want)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -234,7 +242,7 @@ func TestResourceNotStarted(t *testing.T) {
|
||||
|
||||
for _, test := range tests {
|
||||
if err := test.fn(&Parser{}); err != ErrNotStarted {
|
||||
t.Errorf("got _, %v = p.%s(), want = _, %v", err, test.name, ErrNotStarted)
|
||||
t.Errorf("got Parser.%s() = _ , %v, want = _, %v", test.name, err, ErrNotStarted)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -258,15 +266,15 @@ func TestDNSPackUnpack(t *testing.T) {
|
||||
for i, want := range wants {
|
||||
b, err := want.Pack()
|
||||
if err != nil {
|
||||
t.Fatalf("%d: packing failed: %v", i, err)
|
||||
t.Fatalf("%d: Message.Pack() = %v", i, err)
|
||||
}
|
||||
var got Message
|
||||
err = got.Unpack(b)
|
||||
if err != nil {
|
||||
t.Fatalf("%d: unpacking failed: %v", i, err)
|
||||
t.Fatalf("%d: Message.Unapck() = %v", i, err)
|
||||
}
|
||||
if !reflect.DeepEqual(got, want) {
|
||||
t.Errorf("%d: got = %+v, want = %+v", i, &got, &want)
|
||||
t.Errorf("%d: Message.Pack/Unpack() roundtrip: got = %+v, want = %+v", i, &got, &want)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -291,16 +299,16 @@ func TestDNSAppendPackUnpack(t *testing.T) {
|
||||
b := make([]byte, 2, 514)
|
||||
b, err := want.AppendPack(b)
|
||||
if err != nil {
|
||||
t.Fatalf("%d: packing failed: %v", i, err)
|
||||
t.Fatalf("%d: Message.AppendPack() = %v", i, err)
|
||||
}
|
||||
b = b[2:]
|
||||
var got Message
|
||||
err = got.Unpack(b)
|
||||
if err != nil {
|
||||
t.Fatalf("%d: unpacking failed: %v", i, err)
|
||||
t.Fatalf("%d: Message.Unapck() = %v", i, err)
|
||||
}
|
||||
if !reflect.DeepEqual(got, want) {
|
||||
t.Errorf("%d: got = %+v, want = %+v", i, &got, &want)
|
||||
t.Errorf("%d: Message.AppendPack/Unpack() roundtrip: got = %+v, want = %+v", i, &got, &want)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -309,11 +317,11 @@ func TestSkipAll(t *testing.T) {
|
||||
msg := largeTestMsg()
|
||||
buf, err := msg.Pack()
|
||||
if err != nil {
|
||||
t.Fatal("Packing large test message:", err)
|
||||
t.Fatal("Message.Pack() =", err)
|
||||
}
|
||||
var p Parser
|
||||
if _, err := p.Start(buf); err != nil {
|
||||
t.Fatal(err)
|
||||
t.Fatal("Parser.Start(non-nil) =", err)
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
@@ -328,7 +336,7 @@ func TestSkipAll(t *testing.T) {
|
||||
for _, test := range tests {
|
||||
for i := 1; i <= 3; i++ {
|
||||
if err := test.f(); err != nil {
|
||||
t.Errorf("Call #%d to %s(): %v", i, test.name, err)
|
||||
t.Errorf("%d: Parser.%s() = %v", i, test.name, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -339,11 +347,11 @@ func TestSkipEach(t *testing.T) {
|
||||
|
||||
buf, err := msg.Pack()
|
||||
if err != nil {
|
||||
t.Fatal("Packing test message:", err)
|
||||
t.Fatal("Message.Pack() =", err)
|
||||
}
|
||||
var p Parser
|
||||
if _, err := p.Start(buf); err != nil {
|
||||
t.Fatal(err)
|
||||
t.Fatal("Parser.Start(non-nil) =", err)
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
@@ -357,10 +365,10 @@ func TestSkipEach(t *testing.T) {
|
||||
}
|
||||
for _, test := range tests {
|
||||
if err := test.f(); err != nil {
|
||||
t.Errorf("First call: got %s() = %v, want = %v", test.name, err, nil)
|
||||
t.Errorf("first Parser.%s() = %v, want = nil", test.name, err)
|
||||
}
|
||||
if err := test.f(); err != ErrSectionDone {
|
||||
t.Errorf("Second call: got %s() = %v, want = %v", test.name, err, ErrSectionDone)
|
||||
t.Errorf("second Parser.%s() = %v, want = %v", test.name, err, ErrSectionDone)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -370,11 +378,11 @@ func TestSkipAfterRead(t *testing.T) {
|
||||
|
||||
buf, err := msg.Pack()
|
||||
if err != nil {
|
||||
t.Fatal("Packing test message:", err)
|
||||
t.Fatal("Message.Pack() =", err)
|
||||
}
|
||||
var p Parser
|
||||
if _, err := p.Start(buf); err != nil {
|
||||
t.Fatal(err)
|
||||
t.Fatal("Parser.Srart(non-nil) =", err)
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
@@ -389,10 +397,10 @@ func TestSkipAfterRead(t *testing.T) {
|
||||
}
|
||||
for _, test := range tests {
|
||||
if err := test.read(); err != nil {
|
||||
t.Errorf("Got %s() = _, %v, want = _, %v", test.name, err, nil)
|
||||
t.Errorf("got Parser.%s() = _, %v, want = _, nil", test.name, err)
|
||||
}
|
||||
if err := test.skip(); err != ErrSectionDone {
|
||||
t.Errorf("Got Skip%s() = %v, want = %v", test.name, err, ErrSectionDone)
|
||||
t.Errorf("got Parser.Skip%s() = %v, want = %v", test.name, err, ErrSectionDone)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -411,7 +419,7 @@ func TestSkipNotStarted(t *testing.T) {
|
||||
}
|
||||
for _, test := range tests {
|
||||
if err := test.f(); err != ErrNotStarted {
|
||||
t.Errorf("Got %s() = %v, want = %v", test.name, err, ErrNotStarted)
|
||||
t.Errorf("got Parser.%s() = %v, want = %v", test.name, err, ErrNotStarted)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -455,7 +463,7 @@ func TestTooManyRecords(t *testing.T) {
|
||||
|
||||
for _, test := range tests {
|
||||
if _, got := test.msg.Pack(); got != test.want {
|
||||
t.Errorf("Packing %d %s: got = %v, want = %v", recs, test.name, got, test.want)
|
||||
t.Errorf("got Message.Pack() for %d %s = %v, want = %v", recs, test.name, got, test.want)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -479,30 +487,30 @@ func TestVeryLongTxt(t *testing.T) {
|
||||
}
|
||||
buf, err := want.pack(make([]byte, 0, 8000), map[string]int{}, 0)
|
||||
if err != nil {
|
||||
t.Fatal("Packing failed:", err)
|
||||
t.Fatal("Resource.pack() =", err)
|
||||
}
|
||||
var got Resource
|
||||
off, err := got.Header.unpack(buf, 0)
|
||||
if err != nil {
|
||||
t.Fatal("Unpacking ResourceHeader failed:", err)
|
||||
t.Fatal("ResourceHeader.unpack() =", err)
|
||||
}
|
||||
body, n, err := unpackResourceBody(buf, off, got.Header)
|
||||
if err != nil {
|
||||
t.Fatal("Unpacking failed:", err)
|
||||
t.Fatal("unpackResourceBody() =", err)
|
||||
}
|
||||
got.Body = body
|
||||
if n != len(buf) {
|
||||
t.Errorf("Unpacked different amount than packed: got n = %d, want = %d", n, len(buf))
|
||||
t.Errorf("unpacked different amount than packed: got = %d, want = %d", n, len(buf))
|
||||
}
|
||||
if !reflect.DeepEqual(got, want) {
|
||||
t.Errorf("Got = %#v, want = %#v", got, want)
|
||||
t.Errorf("Resource.pack/unpack() roundtrip: got = %#v, want = %#v", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTooLongTxt(t *testing.T) {
|
||||
rb := TXTResource{[]string{strings.Repeat(".", 256)}}
|
||||
if _, err := rb.pack(make([]byte, 0, 8000), map[string]int{}, 0); err != errStringTooLong {
|
||||
t.Errorf("Packing TXTRecord with 256 character string: got err = %v, want = %v", err, errStringTooLong)
|
||||
t.Errorf("packing TXTResource with 256 character string: got err = %v, want = %v", err, errStringTooLong)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -516,13 +524,13 @@ func TestStartAppends(t *testing.T) {
|
||||
|
||||
buf, err := b.Finish()
|
||||
if err != nil {
|
||||
t.Fatal("Building failed:", err)
|
||||
t.Fatal("Builder.Finish() =", err)
|
||||
}
|
||||
if got, want := len(buf), headerLen+2; got != want {
|
||||
t.Errorf("Got len(buf} = %d, want = %d", got, want)
|
||||
t.Errorf("got len(buf) = %d, want = %d", got, want)
|
||||
}
|
||||
if string(buf[:2]) != string(wantBuf) {
|
||||
t.Errorf("Original data not preserved, got = %v, want = %v", buf[:2], wantBuf)
|
||||
t.Errorf("original data not preserved, got = %#v, want = %#v", buf[:2], wantBuf)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -549,7 +557,7 @@ func TestStartError(t *testing.T) {
|
||||
for _, env := range envs {
|
||||
for _, test := range tests {
|
||||
if got := test.fn(env.fn()); got != env.want {
|
||||
t.Errorf("got Builder{%s}.Start%s = %v, want = %v", env.name, test.name, got, env.want)
|
||||
t.Errorf("got Builder{%s}.Start%s() = %v, want = %v", env.name, test.name, got, env.want)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -569,6 +577,7 @@ func TestBuilderResourceError(t *testing.T) {
|
||||
{"SRVResource", func(b *Builder) error { return b.SRVResource(ResourceHeader{}, SRVResource{}) }},
|
||||
{"AResource", func(b *Builder) error { return b.AResource(ResourceHeader{}, AResource{}) }},
|
||||
{"AAAAResource", func(b *Builder) error { return b.AAAAResource(ResourceHeader{}, AAAAResource{}) }},
|
||||
{"OPTResource", func(b *Builder) error { return b.OPTResource(ResourceHeader{}, OPTResource{}) }},
|
||||
}
|
||||
|
||||
envs := []struct {
|
||||
@@ -585,7 +594,7 @@ func TestBuilderResourceError(t *testing.T) {
|
||||
for _, env := range envs {
|
||||
for _, test := range tests {
|
||||
if got := test.fn(env.fn()); got != env.want {
|
||||
t.Errorf("got Builder{%s}.%s = %v, want = %v", env.name, test.name, got, env.want)
|
||||
t.Errorf("got Builder{%s}.%s() = %v, want = %v", env.name, test.name, got, env.want)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -595,7 +604,7 @@ func TestFinishError(t *testing.T) {
|
||||
var b Builder
|
||||
want := ErrNotStarted
|
||||
if _, got := b.Finish(); got != want {
|
||||
t.Errorf("got Builder{}.Finish() = %v, want = %v", got, want)
|
||||
t.Errorf("got Builder.Finish() = %v, want = %v", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -603,89 +612,96 @@ func TestBuilder(t *testing.T) {
|
||||
msg := largeTestMsg()
|
||||
want, err := msg.Pack()
|
||||
if err != nil {
|
||||
t.Fatal("Packing without builder:", err)
|
||||
t.Fatal("Message.Pack() =", err)
|
||||
}
|
||||
|
||||
b := NewBuilder(nil, msg.Header)
|
||||
b.EnableCompression()
|
||||
|
||||
if err := b.StartQuestions(); err != nil {
|
||||
t.Fatal("b.StartQuestions():", err)
|
||||
t.Fatal("Builder.StartQuestions() =", err)
|
||||
}
|
||||
for _, q := range msg.Questions {
|
||||
if err := b.Question(q); err != nil {
|
||||
t.Fatalf("b.Question(%#v): %v", q, err)
|
||||
t.Fatalf("Builder.Question(%#v) = %v", q, err)
|
||||
}
|
||||
}
|
||||
|
||||
if err := b.StartAnswers(); err != nil {
|
||||
t.Fatal("b.StartAnswers():", err)
|
||||
t.Fatal("Builder.StartAnswers() =", err)
|
||||
}
|
||||
for _, a := range msg.Answers {
|
||||
switch a.Header.Type {
|
||||
case TypeA:
|
||||
if err := b.AResource(a.Header, *a.Body.(*AResource)); err != nil {
|
||||
t.Fatalf("b.AResource(%#v): %v", a, err)
|
||||
t.Fatalf("Builder.AResource(%#v) = %v", a, err)
|
||||
}
|
||||
case TypeNS:
|
||||
if err := b.NSResource(a.Header, *a.Body.(*NSResource)); err != nil {
|
||||
t.Fatalf("b.NSResource(%#v): %v", a, err)
|
||||
t.Fatalf("Builder.NSResource(%#v) = %v", a, err)
|
||||
}
|
||||
case TypeCNAME:
|
||||
if err := b.CNAMEResource(a.Header, *a.Body.(*CNAMEResource)); err != nil {
|
||||
t.Fatalf("b.CNAMEResource(%#v): %v", a, err)
|
||||
t.Fatalf("Builder.CNAMEResource(%#v) = %v", a, err)
|
||||
}
|
||||
case TypeSOA:
|
||||
if err := b.SOAResource(a.Header, *a.Body.(*SOAResource)); err != nil {
|
||||
t.Fatalf("b.SOAResource(%#v): %v", a, err)
|
||||
t.Fatalf("Builder.SOAResource(%#v) = %v", a, err)
|
||||
}
|
||||
case TypePTR:
|
||||
if err := b.PTRResource(a.Header, *a.Body.(*PTRResource)); err != nil {
|
||||
t.Fatalf("b.PTRResource(%#v): %v", a, err)
|
||||
t.Fatalf("Builder.PTRResource(%#v) = %v", a, err)
|
||||
}
|
||||
case TypeMX:
|
||||
if err := b.MXResource(a.Header, *a.Body.(*MXResource)); err != nil {
|
||||
t.Fatalf("b.MXResource(%#v): %v", a, err)
|
||||
t.Fatalf("Builder.MXResource(%#v) = %v", a, err)
|
||||
}
|
||||
case TypeTXT:
|
||||
if err := b.TXTResource(a.Header, *a.Body.(*TXTResource)); err != nil {
|
||||
t.Fatalf("b.TXTResource(%#v): %v", a, err)
|
||||
t.Fatalf("Builder.TXTResource(%#v) = %v", a, err)
|
||||
}
|
||||
case TypeAAAA:
|
||||
if err := b.AAAAResource(a.Header, *a.Body.(*AAAAResource)); err != nil {
|
||||
t.Fatalf("b.AAAAResource(%#v): %v", a, err)
|
||||
t.Fatalf("Builder.AAAAResource(%#v) = %v", a, err)
|
||||
}
|
||||
case TypeSRV:
|
||||
if err := b.SRVResource(a.Header, *a.Body.(*SRVResource)); err != nil {
|
||||
t.Fatalf("b.SRVResource(%#v): %v", a, err)
|
||||
t.Fatalf("Builder.SRVResource(%#v) = %v", a, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if err := b.StartAuthorities(); err != nil {
|
||||
t.Fatal("b.StartAuthorities():", err)
|
||||
t.Fatal("Builder.StartAuthorities() =", err)
|
||||
}
|
||||
for _, a := range msg.Authorities {
|
||||
if err := b.NSResource(a.Header, *a.Body.(*NSResource)); err != nil {
|
||||
t.Fatalf("b.NSResource(%#v): %v", a, err)
|
||||
t.Fatalf("Builder.NSResource(%#v) = %v", a, err)
|
||||
}
|
||||
}
|
||||
|
||||
if err := b.StartAdditionals(); err != nil {
|
||||
t.Fatal("b.StartAdditionals():", err)
|
||||
t.Fatal("Builder.StartAdditionals() =", err)
|
||||
}
|
||||
for _, a := range msg.Additionals {
|
||||
if err := b.TXTResource(a.Header, *a.Body.(*TXTResource)); err != nil {
|
||||
t.Fatalf("b.TXTResource(%#v): %v", a, err)
|
||||
switch a.Body.(type) {
|
||||
case *TXTResource:
|
||||
if err := b.TXTResource(a.Header, *a.Body.(*TXTResource)); err != nil {
|
||||
t.Fatalf("Builder.TXTResource(%#v) = %v", a, err)
|
||||
}
|
||||
case *OPTResource:
|
||||
if err := b.OPTResource(a.Header, *a.Body.(*OPTResource)); err != nil {
|
||||
t.Fatalf("Builder.OPTResource(%#v) = %v", a, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
got, err := b.Finish()
|
||||
if err != nil {
|
||||
t.Fatal("b.Finish():", err)
|
||||
t.Fatal("Builder.Finish() =", err)
|
||||
}
|
||||
if !bytes.Equal(got, want) {
|
||||
t.Fatalf("Got from Builder: %#v\nwant = %#v", got, want)
|
||||
t.Fatalf("got from Builder.Finish() = %#v\nwant = %#v", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -740,7 +756,146 @@ func TestResourcePack(t *testing.T) {
|
||||
} {
|
||||
_, err := tt.m.Pack()
|
||||
if !reflect.DeepEqual(err, tt.err) {
|
||||
t.Errorf("got %v for %v; want %v", err, tt.m, tt.err)
|
||||
t.Errorf("got Message{%v}.Pack() = %v, want %v", tt.m, err, tt.err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestOptionPackUnpack(t *testing.T) {
|
||||
for _, tt := range []struct {
|
||||
name string
|
||||
w []byte // wire format of m.Additionals
|
||||
m Message
|
||||
dnssecOK bool
|
||||
extRCode RCode
|
||||
}{
|
||||
{
|
||||
name: "without EDNS(0) options",
|
||||
w: []byte{
|
||||
0x00, 0x00, 0x29, 0x10, 0x00, 0xfe, 0x00, 0x80,
|
||||
0x00, 0x00, 0x00,
|
||||
},
|
||||
m: Message{
|
||||
Header: Header{RCode: RCodeFormatError},
|
||||
Questions: []Question{
|
||||
{
|
||||
Name: mustNewName("."),
|
||||
Type: TypeA,
|
||||
Class: ClassINET,
|
||||
},
|
||||
},
|
||||
Additionals: []Resource{
|
||||
{
|
||||
mustEDNS0ResourceHeader(4096, 0xfe0|RCodeFormatError, true),
|
||||
&OPTResource{},
|
||||
},
|
||||
},
|
||||
},
|
||||
dnssecOK: true,
|
||||
extRCode: 0xfe0 | RCodeFormatError,
|
||||
},
|
||||
{
|
||||
name: "with EDNS(0) options",
|
||||
w: []byte{
|
||||
0x00, 0x00, 0x29, 0x10, 0x00, 0xff, 0x00, 0x00,
|
||||
0x00, 0x00, 0x0c, 0x00, 0x0c, 0x00, 0x02, 0x00,
|
||||
0x00, 0x00, 0x0b, 0x00, 0x02, 0x12, 0x34,
|
||||
},
|
||||
m: Message{
|
||||
Header: Header{RCode: RCodeServerFailure},
|
||||
Questions: []Question{
|
||||
{
|
||||
Name: mustNewName("."),
|
||||
Type: TypeAAAA,
|
||||
Class: ClassINET,
|
||||
},
|
||||
},
|
||||
Additionals: []Resource{
|
||||
{
|
||||
mustEDNS0ResourceHeader(4096, 0xff0|RCodeServerFailure, false),
|
||||
&OPTResource{
|
||||
Options: []Option{
|
||||
{
|
||||
Code: 12, // see RFC 7828
|
||||
Data: []byte{0x00, 0x00},
|
||||
},
|
||||
{
|
||||
Code: 11, // see RFC 7830
|
||||
Data: []byte{0x12, 0x34},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
dnssecOK: false,
|
||||
extRCode: 0xff0 | RCodeServerFailure,
|
||||
},
|
||||
{
|
||||
// Containing multiple OPT resources in a
|
||||
// message is invalid, but it's necessary for
|
||||
// protocol conformance testing.
|
||||
name: "with multiple OPT resources",
|
||||
w: []byte{
|
||||
0x00, 0x00, 0x29, 0x10, 0x00, 0xff, 0x00, 0x00,
|
||||
0x00, 0x00, 0x06, 0x00, 0x0b, 0x00, 0x02, 0x12,
|
||||
0x34, 0x00, 0x00, 0x29, 0x10, 0x00, 0xff, 0x00,
|
||||
0x00, 0x00, 0x00, 0x06, 0x00, 0x0c, 0x00, 0x02,
|
||||
0x00, 0x00,
|
||||
},
|
||||
m: Message{
|
||||
Header: Header{RCode: RCodeNameError},
|
||||
Questions: []Question{
|
||||
{
|
||||
Name: mustNewName("."),
|
||||
Type: TypeAAAA,
|
||||
Class: ClassINET,
|
||||
},
|
||||
},
|
||||
Additionals: []Resource{
|
||||
{
|
||||
mustEDNS0ResourceHeader(4096, 0xff0|RCodeNameError, false),
|
||||
&OPTResource{
|
||||
Options: []Option{
|
||||
{
|
||||
Code: 11, // see RFC 7830
|
||||
Data: []byte{0x12, 0x34},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
mustEDNS0ResourceHeader(4096, 0xff0|RCodeNameError, false),
|
||||
&OPTResource{
|
||||
Options: []Option{
|
||||
{
|
||||
Code: 12, // see RFC 7828
|
||||
Data: []byte{0x00, 0x00},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
} {
|
||||
w, err := tt.m.Pack()
|
||||
if err != nil {
|
||||
t.Errorf("Message.Pack() for %s = %v", tt.name, err)
|
||||
continue
|
||||
}
|
||||
if !bytes.Equal(w[len(w)-len(tt.w):], tt.w) {
|
||||
t.Errorf("got Message.Pack() for %s = %#v, want %#v", tt.name, w[len(w)-len(tt.w):], tt.w)
|
||||
continue
|
||||
}
|
||||
var m Message
|
||||
if err := m.Unpack(w); err != nil {
|
||||
t.Errorf("Message.Unpack() for %s = %v", tt.name, err)
|
||||
continue
|
||||
}
|
||||
if !reflect.DeepEqual(m.Additionals, tt.m.Additionals) {
|
||||
t.Errorf("got Message.Pack/Unpack() roundtrip for %s = %+v, want %+v", tt.name, m, tt.m)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -790,7 +945,7 @@ func benchmarkParsingSetup() ([]byte, error) {
|
||||
|
||||
buf, err := msg.Pack()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("msg.Pack(): %v", err)
|
||||
return nil, fmt.Errorf("Message.Pack() = %v", err)
|
||||
}
|
||||
return buf, nil
|
||||
}
|
||||
@@ -798,7 +953,7 @@ func benchmarkParsingSetup() ([]byte, error) {
|
||||
func benchmarkParsing(tb testing.TB, buf []byte) {
|
||||
var p Parser
|
||||
if _, err := p.Start(buf); err != nil {
|
||||
tb.Fatal("p.Start(buf):", err)
|
||||
tb.Fatal("Parser.Start(non-nil) =", err)
|
||||
}
|
||||
|
||||
for {
|
||||
@@ -807,7 +962,7 @@ func benchmarkParsing(tb testing.TB, buf []byte) {
|
||||
break
|
||||
}
|
||||
if err != nil {
|
||||
tb.Fatal("p.Question():", err)
|
||||
tb.Fatal("Parser.Question() =", err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -817,28 +972,32 @@ func benchmarkParsing(tb testing.TB, buf []byte) {
|
||||
break
|
||||
}
|
||||
if err != nil {
|
||||
panic(err)
|
||||
tb.Fatal("Parser.AnswerHeader() =", err)
|
||||
}
|
||||
|
||||
switch h.Type {
|
||||
case TypeA:
|
||||
if _, err := p.AResource(); err != nil {
|
||||
tb.Fatal("p.AResource():", err)
|
||||
tb.Fatal("Parser.AResource() =", err)
|
||||
}
|
||||
case TypeAAAA:
|
||||
if _, err := p.AAAAResource(); err != nil {
|
||||
tb.Fatal("p.AAAAResource():", err)
|
||||
tb.Fatal("Parser.AAAAResource() =", err)
|
||||
}
|
||||
case TypeCNAME:
|
||||
if _, err := p.CNAMEResource(); err != nil {
|
||||
tb.Fatal("p.CNAMEResource():", err)
|
||||
tb.Fatal("Parser.CNAMEResource() =", err)
|
||||
}
|
||||
case TypeNS:
|
||||
if _, err := p.NSResource(); err != nil {
|
||||
tb.Fatal("p.NSResource():", err)
|
||||
tb.Fatal("Parser.NSResource() =", err)
|
||||
}
|
||||
case TypeOPT:
|
||||
if _, err := p.OPTResource(); err != nil {
|
||||
tb.Fatal("Parser.OPTResource() =", err)
|
||||
}
|
||||
default:
|
||||
tb.Fatalf("unknown type: %T", h)
|
||||
tb.Fatalf("got unknown type: %T", h)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -862,7 +1021,7 @@ func TestParsingAllocs(t *testing.T) {
|
||||
}
|
||||
|
||||
if allocs := testing.AllocsPerRun(100, func() { benchmarkParsing(t, buf) }); allocs > 0.5 {
|
||||
t.Errorf("Allocations during parsing: got = %f, want ~0", allocs)
|
||||
t.Errorf("allocations during parsing: got = %f, want ~0", allocs)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -876,7 +1035,7 @@ func benchmarkBuilding(tb testing.TB, name Name, buf []byte) {
|
||||
bld := NewBuilder(buf, Header{Response: true, Authoritative: true})
|
||||
|
||||
if err := bld.StartQuestions(); err != nil {
|
||||
tb.Fatal("bld.StartQuestions():", err)
|
||||
tb.Fatal("Builder.StartQuestions() =", err)
|
||||
}
|
||||
q := Question{
|
||||
Name: name,
|
||||
@@ -884,7 +1043,7 @@ func benchmarkBuilding(tb testing.TB, name Name, buf []byte) {
|
||||
Class: ClassINET,
|
||||
}
|
||||
if err := bld.Question(q); err != nil {
|
||||
tb.Fatalf("bld.Question(%+v): %v", q, err)
|
||||
tb.Fatalf("Builder.Question(%+v) = %v", q, err)
|
||||
}
|
||||
|
||||
hdr := ResourceHeader{
|
||||
@@ -892,31 +1051,40 @@ func benchmarkBuilding(tb testing.TB, name Name, buf []byte) {
|
||||
Class: ClassINET,
|
||||
}
|
||||
if err := bld.StartAnswers(); err != nil {
|
||||
tb.Fatal("bld.StartQuestions():", err)
|
||||
tb.Fatal("Builder.StartQuestions() =", err)
|
||||
}
|
||||
|
||||
ar := AResource{[4]byte{}}
|
||||
if err := bld.AResource(hdr, ar); err != nil {
|
||||
tb.Fatalf("bld.AResource(%+v, %+v): %v", hdr, ar, err)
|
||||
tb.Fatalf("Builder.AResource(%+v, %+v) = %v", hdr, ar, err)
|
||||
}
|
||||
|
||||
aaar := AAAAResource{[16]byte{}}
|
||||
if err := bld.AAAAResource(hdr, aaar); err != nil {
|
||||
tb.Fatalf("bld.AAAAResource(%+v, %+v): %v", hdr, aaar, err)
|
||||
tb.Fatalf("Builder.AAAAResource(%+v, %+v) = %v", hdr, aaar, err)
|
||||
}
|
||||
|
||||
cnr := CNAMEResource{name}
|
||||
if err := bld.CNAMEResource(hdr, cnr); err != nil {
|
||||
tb.Fatalf("bld.CNAMEResource(%+v, %+v): %v", hdr, cnr, err)
|
||||
tb.Fatalf("Builder.CNAMEResource(%+v, %+v) = %v", hdr, cnr, err)
|
||||
}
|
||||
|
||||
nsr := NSResource{name}
|
||||
if err := bld.NSResource(hdr, nsr); err != nil {
|
||||
tb.Fatalf("bld.NSResource(%+v, %+v): %v", hdr, nsr, err)
|
||||
tb.Fatalf("Builder.NSResource(%+v, %+v) = %v", hdr, nsr, err)
|
||||
}
|
||||
|
||||
extrc := 0xfe0 | RCodeNotImplemented
|
||||
if err := (&hdr).SetEDNS0(4096, extrc, true); err != nil {
|
||||
tb.Fatalf("ResourceHeader.SetEDNS0(4096, %#x, true) = %v", extrc, err)
|
||||
}
|
||||
optr := OPTResource{}
|
||||
if err := bld.OPTResource(hdr, optr); err != nil {
|
||||
tb.Fatalf("Builder.OPTResource(%+v, %+v) = %v", hdr, optr, err)
|
||||
}
|
||||
|
||||
if _, err := bld.Finish(); err != nil {
|
||||
tb.Fatal("bld.Finish():", err)
|
||||
tb.Fatal("Builder.Finish() =", err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -931,7 +1099,7 @@ func BenchmarkBuilding(b *testing.B) {
|
||||
func TestBuildingAllocs(t *testing.T) {
|
||||
name, buf := benchmarkBuildingSetup()
|
||||
if allocs := testing.AllocsPerRun(100, func() { benchmarkBuilding(t, name, buf) }); allocs > 0.5 {
|
||||
t.Errorf("Allocations during building: got = %f, want ~0", allocs)
|
||||
t.Errorf("allocations during building: got = %f, want ~0", allocs)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -986,7 +1154,7 @@ func BenchmarkPack(b *testing.B) {
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
if _, err := msg.Pack(); err != nil {
|
||||
b.Fatal(err)
|
||||
b.Fatal("Message.Pack() =", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -999,7 +1167,7 @@ func BenchmarkAppendPack(b *testing.B) {
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
if _, err := msg.AppendPack(buf[:0]); err != nil {
|
||||
b.Fatal(err)
|
||||
b.Fatal("Message.AppendPack() = ", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1132,6 +1300,17 @@ func largeTestMsg() Message {
|
||||
},
|
||||
&TXTResource{[]string{"Hamster Huey and the Gooey Kablooie"}},
|
||||
},
|
||||
{
|
||||
mustEDNS0ResourceHeader(4096, 0xfe0|RCodeSuccess, false),
|
||||
&OPTResource{
|
||||
Options: []Option{
|
||||
{
|
||||
Code: 10, // see RFC 7873
|
||||
Data: []byte{0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
+3
-1
@@ -306,7 +306,7 @@ func (t *table) push(i uint32, depth int) bool {
|
||||
|
||||
// The lists of element names and attribute keys were taken from
|
||||
// https://html.spec.whatwg.org/multipage/indices.html#index
|
||||
// as of the "HTML Living Standard - Last Updated 18 September 2017" version.
|
||||
// as of the "HTML Living Standard - Last Updated 16 April 2018" version.
|
||||
|
||||
// "command", "keygen" and "menuitem" have been removed from the spec,
|
||||
// but are kept here for backwards compatibility.
|
||||
@@ -701,6 +701,8 @@ var extra = []string{
|
||||
"plaintext",
|
||||
"prompt",
|
||||
"public",
|
||||
"rb",
|
||||
"rtc",
|
||||
"spacer",
|
||||
"strike",
|
||||
"svg",
|
||||
|
||||
+552
-548
File diff suppressed because it is too large
Load Diff
+2
@@ -296,6 +296,7 @@ var testAtomList = []string{
|
||||
"public",
|
||||
"q",
|
||||
"radiogroup",
|
||||
"rb",
|
||||
"readonly",
|
||||
"referrerpolicy",
|
||||
"rel",
|
||||
@@ -305,6 +306,7 @@ var testAtomList = []string{
|
||||
"rowspan",
|
||||
"rp",
|
||||
"rt",
|
||||
"rtc",
|
||||
"ruby",
|
||||
"s",
|
||||
"samp",
|
||||
|
||||
+26
@@ -174,6 +174,16 @@ func (s *nodeStack) index(n *Node) int {
|
||||
return -1
|
||||
}
|
||||
|
||||
// contains returns whether a is within s.
|
||||
func (s *nodeStack) contains(a atom.Atom) bool {
|
||||
for _, n := range *s {
|
||||
if n.DataAtom == a {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// insert inserts a node at the given index.
|
||||
func (s *nodeStack) insert(i int, n *Node) {
|
||||
(*s) = append(*s, nil)
|
||||
@@ -192,3 +202,19 @@ func (s *nodeStack) remove(n *Node) {
|
||||
(*s)[j] = nil
|
||||
*s = (*s)[:j]
|
||||
}
|
||||
|
||||
type insertionModeStack []insertionMode
|
||||
|
||||
func (s *insertionModeStack) pop() (im insertionMode) {
|
||||
i := len(*s)
|
||||
im = (*s)[i-1]
|
||||
*s = (*s)[:i-1]
|
||||
return im
|
||||
}
|
||||
|
||||
func (s *insertionModeStack) top() insertionMode {
|
||||
if i := len(*s); i > 0 {
|
||||
return (*s)[i-1]
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
+248
-37
@@ -32,6 +32,8 @@ type parser struct {
|
||||
head, form *Node
|
||||
// Other parsing state flags (section 12.2.4.5).
|
||||
scripting, framesetOK bool
|
||||
// The stack of template insertion modes
|
||||
templateStack insertionModeStack
|
||||
// im is the current insertion mode.
|
||||
im insertionMode
|
||||
// originalIM is the insertion mode to go back to after completing a text
|
||||
@@ -126,7 +128,7 @@ func (p *parser) indexOfElementInScope(s scope, matchTags ...a.Atom) int {
|
||||
return -1
|
||||
}
|
||||
case tableScope:
|
||||
if tagAtom == a.Html || tagAtom == a.Table {
|
||||
if tagAtom == a.Html || tagAtom == a.Table || tagAtom == a.Template {
|
||||
return -1
|
||||
}
|
||||
case selectScope:
|
||||
@@ -162,17 +164,17 @@ func (p *parser) clearStackToContext(s scope) {
|
||||
tagAtom := p.oe[i].DataAtom
|
||||
switch s {
|
||||
case tableScope:
|
||||
if tagAtom == a.Html || tagAtom == a.Table {
|
||||
if tagAtom == a.Html || tagAtom == a.Table || tagAtom == a.Template {
|
||||
p.oe = p.oe[:i+1]
|
||||
return
|
||||
}
|
||||
case tableRowScope:
|
||||
if tagAtom == a.Html || tagAtom == a.Tr {
|
||||
if tagAtom == a.Html || tagAtom == a.Tr || tagAtom == a.Template {
|
||||
p.oe = p.oe[:i+1]
|
||||
return
|
||||
}
|
||||
case tableBodyScope:
|
||||
if tagAtom == a.Html || tagAtom == a.Tbody || tagAtom == a.Tfoot || tagAtom == a.Thead {
|
||||
if tagAtom == a.Html || tagAtom == a.Tbody || tagAtom == a.Tfoot || tagAtom == a.Thead || tagAtom == a.Template {
|
||||
p.oe = p.oe[:i+1]
|
||||
return
|
||||
}
|
||||
@@ -183,7 +185,7 @@ func (p *parser) clearStackToContext(s scope) {
|
||||
}
|
||||
|
||||
// generateImpliedEndTags pops nodes off the stack of open elements as long as
|
||||
// the top node has a tag name of dd, dt, li, option, optgroup, p, rp, or rt.
|
||||
// the top node has a tag name of dd, dt, li, optgroup, option, p, rb, rp, rt or rtc.
|
||||
// If exceptions are specified, nodes with that name will not be popped off.
|
||||
func (p *parser) generateImpliedEndTags(exceptions ...string) {
|
||||
var i int
|
||||
@@ -192,7 +194,7 @@ loop:
|
||||
n := p.oe[i]
|
||||
if n.Type == ElementNode {
|
||||
switch n.DataAtom {
|
||||
case a.Dd, a.Dt, a.Li, a.Option, a.Optgroup, a.P, a.Rp, a.Rt:
|
||||
case a.Dd, a.Dt, a.Li, a.Optgroup, a.Option, a.P, a.Rb, a.Rp, a.Rt, a.Rtc:
|
||||
for _, except := range exceptions {
|
||||
if n.Data == except {
|
||||
break loop
|
||||
@@ -207,6 +209,27 @@ loop:
|
||||
p.oe = p.oe[:i+1]
|
||||
}
|
||||
|
||||
// generateAllImpliedEndTags pops nodes off the stack of open elements as long as
|
||||
// the top node has a tag name of caption, colgroup, dd, div, dt, li, optgroup, option, p, rb,
|
||||
// rp, rt, rtc, span, tbody, td, tfoot, th, thead or tr.
|
||||
func (p *parser) generateAllImpliedEndTags() {
|
||||
var i int
|
||||
for i = len(p.oe) - 1; i >= 0; i-- {
|
||||
n := p.oe[i]
|
||||
if n.Type == ElementNode {
|
||||
switch n.DataAtom {
|
||||
// TODO: remove this divergence from the HTML5 spec
|
||||
case a.Caption, a.Colgroup, a.Dd, a.Div, a.Dt, a.Li, a.Optgroup, a.Option, a.P, a.Rb,
|
||||
a.Rp, a.Rt, a.Rtc, a.Span, a.Tbody, a.Td, a.Tfoot, a.Th, a.Thead, a.Tr:
|
||||
continue
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
p.oe = p.oe[:i+1]
|
||||
}
|
||||
|
||||
// addChild adds a child node n to the top element, and pushes n onto the stack
|
||||
// of open elements if it is an element node.
|
||||
func (p *parser) addChild(n *Node) {
|
||||
@@ -236,7 +259,7 @@ func (p *parser) shouldFosterParent() bool {
|
||||
// fosterParent adds a child node according to the foster parenting rules.
|
||||
// Section 12.2.6.1, "foster parenting".
|
||||
func (p *parser) fosterParent(n *Node) {
|
||||
var table, parent, prev *Node
|
||||
var table, parent, prev, template *Node
|
||||
var i int
|
||||
for i = len(p.oe) - 1; i >= 0; i-- {
|
||||
if p.oe[i].DataAtom == a.Table {
|
||||
@@ -245,6 +268,19 @@ func (p *parser) fosterParent(n *Node) {
|
||||
}
|
||||
}
|
||||
|
||||
var j int
|
||||
for j = len(p.oe) - 1; j >= 0; j-- {
|
||||
if p.oe[j].DataAtom == a.Template {
|
||||
template = p.oe[j]
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if template != nil && (table == nil || j < i) {
|
||||
template.AppendChild(n)
|
||||
return
|
||||
}
|
||||
|
||||
if table == nil {
|
||||
// The foster parent is the html element.
|
||||
parent = p.oe[0]
|
||||
@@ -415,14 +451,34 @@ func (p *parser) setOriginalIM() {
|
||||
func (p *parser) resetInsertionMode() {
|
||||
for i := len(p.oe) - 1; i >= 0; i-- {
|
||||
n := p.oe[i]
|
||||
if i == 0 && p.context != nil {
|
||||
last := i == 0
|
||||
if last && p.context != nil {
|
||||
n = p.context
|
||||
}
|
||||
|
||||
switch n.DataAtom {
|
||||
case a.Select:
|
||||
if !last {
|
||||
for ancestor, first := n, p.oe[0]; ancestor != first; {
|
||||
if ancestor == first {
|
||||
break
|
||||
}
|
||||
ancestor = p.oe[p.oe.index(ancestor)-1]
|
||||
switch ancestor.DataAtom {
|
||||
case a.Template:
|
||||
p.im = inSelectIM
|
||||
return
|
||||
case a.Table:
|
||||
p.im = inSelectInTableIM
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
p.im = inSelectIM
|
||||
case a.Td, a.Th:
|
||||
// TODO: remove this divergence from the HTML5 spec.
|
||||
//
|
||||
// See https://bugs.chromium.org/p/chromium/issues/detail?id=829668
|
||||
p.im = inCellIM
|
||||
case a.Tr:
|
||||
p.im = inRowIM
|
||||
@@ -434,20 +490,32 @@ func (p *parser) resetInsertionMode() {
|
||||
p.im = inColumnGroupIM
|
||||
case a.Table:
|
||||
p.im = inTableIM
|
||||
case a.Template:
|
||||
p.im = p.templateStack.top()
|
||||
case a.Head:
|
||||
p.im = inBodyIM
|
||||
// TODO: remove this divergence from the HTML5 spec.
|
||||
//
|
||||
// See https://bugs.chromium.org/p/chromium/issues/detail?id=829668
|
||||
p.im = inHeadIM
|
||||
case a.Body:
|
||||
p.im = inBodyIM
|
||||
case a.Frameset:
|
||||
p.im = inFramesetIM
|
||||
case a.Html:
|
||||
p.im = beforeHeadIM
|
||||
if p.head == nil {
|
||||
p.im = beforeHeadIM
|
||||
} else {
|
||||
p.im = afterHeadIM
|
||||
}
|
||||
default:
|
||||
if last {
|
||||
p.im = inBodyIM
|
||||
return
|
||||
}
|
||||
continue
|
||||
}
|
||||
return
|
||||
}
|
||||
p.im = inBodyIM
|
||||
}
|
||||
|
||||
const whitespace = " \t\r\n\f"
|
||||
@@ -590,19 +658,36 @@ func inHeadIM(p *parser) bool {
|
||||
case a.Head:
|
||||
// Ignore the token.
|
||||
return true
|
||||
case a.Template:
|
||||
p.addElement()
|
||||
p.afe = append(p.afe, &scopeMarker)
|
||||
p.framesetOK = false
|
||||
p.im = inTemplateIM
|
||||
p.templateStack = append(p.templateStack, inTemplateIM)
|
||||
return true
|
||||
}
|
||||
case EndTagToken:
|
||||
switch p.tok.DataAtom {
|
||||
case a.Head:
|
||||
n := p.oe.pop()
|
||||
if n.DataAtom != a.Head {
|
||||
panic("html: bad parser state: <head> element not found, in the in-head insertion mode")
|
||||
}
|
||||
p.oe.pop()
|
||||
p.im = afterHeadIM
|
||||
return true
|
||||
case a.Body, a.Html, a.Br:
|
||||
p.parseImpliedToken(EndTagToken, a.Head, a.Head.String())
|
||||
return false
|
||||
case a.Template:
|
||||
if !p.oe.contains(a.Template) {
|
||||
return true
|
||||
}
|
||||
p.generateAllImpliedEndTags()
|
||||
if n := p.oe.top(); n.DataAtom != a.Template {
|
||||
return true
|
||||
}
|
||||
p.popUntil(defaultScope, a.Template)
|
||||
p.clearActiveFormattingElements()
|
||||
p.templateStack.pop()
|
||||
p.resetInsertionMode()
|
||||
return true
|
||||
default:
|
||||
// Ignore the token.
|
||||
return true
|
||||
@@ -648,7 +733,7 @@ func afterHeadIM(p *parser) bool {
|
||||
p.addElement()
|
||||
p.im = inFramesetIM
|
||||
return true
|
||||
case a.Base, a.Basefont, a.Bgsound, a.Link, a.Meta, a.Noframes, a.Script, a.Style, a.Title:
|
||||
case a.Base, a.Basefont, a.Bgsound, a.Link, a.Meta, a.Noframes, a.Script, a.Style, a.Template, a.Title:
|
||||
p.oe = append(p.oe, p.head)
|
||||
defer p.oe.remove(p.head)
|
||||
return inHeadIM(p)
|
||||
@@ -660,6 +745,8 @@ func afterHeadIM(p *parser) bool {
|
||||
switch p.tok.DataAtom {
|
||||
case a.Body, a.Html, a.Br:
|
||||
// Drop down to creating an implied <body> tag.
|
||||
case a.Template:
|
||||
return inHeadIM(p)
|
||||
default:
|
||||
// Ignore the token.
|
||||
return true
|
||||
@@ -727,10 +814,16 @@ func inBodyIM(p *parser) bool {
|
||||
case StartTagToken:
|
||||
switch p.tok.DataAtom {
|
||||
case a.Html:
|
||||
if p.oe.contains(a.Template) {
|
||||
return true
|
||||
}
|
||||
copyAttributes(p.oe[0], p.tok)
|
||||
case a.Base, a.Basefont, a.Bgsound, a.Command, a.Link, a.Meta, a.Noframes, a.Script, a.Style, a.Title:
|
||||
case a.Base, a.Basefont, a.Bgsound, a.Command, a.Link, a.Meta, a.Noframes, a.Script, a.Style, a.Template, a.Title:
|
||||
return inHeadIM(p)
|
||||
case a.Body:
|
||||
if p.oe.contains(a.Template) {
|
||||
return true
|
||||
}
|
||||
if len(p.oe) >= 2 {
|
||||
body := p.oe[1]
|
||||
if body.Type == ElementNode && body.DataAtom == a.Body {
|
||||
@@ -767,7 +860,7 @@ func inBodyIM(p *parser) bool {
|
||||
// The newline, if any, will be dealt with by the TextToken case.
|
||||
p.framesetOK = false
|
||||
case a.Form:
|
||||
if p.form == nil {
|
||||
if p.oe.contains(a.Template) || p.form == nil {
|
||||
p.popUntil(buttonScope, a.P)
|
||||
p.addElement()
|
||||
p.form = p.top()
|
||||
@@ -952,11 +1045,16 @@ func inBodyIM(p *parser) bool {
|
||||
}
|
||||
p.reconstructActiveFormattingElements()
|
||||
p.addElement()
|
||||
case a.Rp, a.Rt:
|
||||
case a.Rb, a.Rtc:
|
||||
if p.elementInScope(defaultScope, a.Ruby) {
|
||||
p.generateImpliedEndTags()
|
||||
}
|
||||
p.addElement()
|
||||
case a.Rp, a.Rt:
|
||||
if p.elementInScope(defaultScope, a.Ruby) {
|
||||
p.generateImpliedEndTags("rtc")
|
||||
}
|
||||
p.addElement()
|
||||
case a.Math, a.Svg:
|
||||
p.reconstructActiveFormattingElements()
|
||||
if p.tok.DataAtom == a.Math {
|
||||
@@ -972,7 +1070,13 @@ func inBodyIM(p *parser) bool {
|
||||
p.acknowledgeSelfClosingTag()
|
||||
}
|
||||
return true
|
||||
case a.Caption, a.Col, a.Colgroup, a.Frame, a.Head, a.Tbody, a.Td, a.Tfoot, a.Th, a.Thead, a.Tr:
|
||||
case a.Frame:
|
||||
// TODO: remove this divergence from the HTML5 spec.
|
||||
if p.oe.contains(a.Template) {
|
||||
p.addElement()
|
||||
return true
|
||||
}
|
||||
case a.Caption, a.Col, a.Colgroup, a.Head, a.Tbody, a.Td, a.Tfoot, a.Th, a.Thead, a.Tr:
|
||||
// Ignore the token.
|
||||
default:
|
||||
p.reconstructActiveFormattingElements()
|
||||
@@ -993,15 +1097,28 @@ func inBodyIM(p *parser) bool {
|
||||
case a.Address, a.Article, a.Aside, a.Blockquote, a.Button, a.Center, a.Details, a.Dir, a.Div, a.Dl, a.Fieldset, a.Figcaption, a.Figure, a.Footer, a.Header, a.Hgroup, a.Listing, a.Menu, a.Nav, a.Ol, a.Pre, a.Section, a.Summary, a.Ul:
|
||||
p.popUntil(defaultScope, p.tok.DataAtom)
|
||||
case a.Form:
|
||||
node := p.form
|
||||
p.form = nil
|
||||
i := p.indexOfElementInScope(defaultScope, a.Form)
|
||||
if node == nil || i == -1 || p.oe[i] != node {
|
||||
// Ignore the token.
|
||||
return true
|
||||
if p.oe.contains(a.Template) {
|
||||
if !p.oe.contains(a.Form) {
|
||||
// Ignore the token.
|
||||
return true
|
||||
}
|
||||
p.generateImpliedEndTags()
|
||||
if p.tok.DataAtom == a.Form {
|
||||
// Ignore the token.
|
||||
return true
|
||||
}
|
||||
p.popUntil(defaultScope, a.Form)
|
||||
} else {
|
||||
node := p.form
|
||||
p.form = nil
|
||||
i := p.indexOfElementInScope(defaultScope, a.Form)
|
||||
if node == nil || i == -1 || p.oe[i] != node {
|
||||
// Ignore the token.
|
||||
return true
|
||||
}
|
||||
p.generateImpliedEndTags()
|
||||
p.oe.remove(node)
|
||||
}
|
||||
p.generateImpliedEndTags()
|
||||
p.oe.remove(node)
|
||||
case a.P:
|
||||
if !p.elementInScope(buttonScope, a.P) {
|
||||
p.parseImpliedToken(StartTagToken, a.P, a.P.String())
|
||||
@@ -1022,6 +1139,8 @@ func inBodyIM(p *parser) bool {
|
||||
case a.Br:
|
||||
p.tok.Type = StartTagToken
|
||||
return false
|
||||
case a.Template:
|
||||
return inHeadIM(p)
|
||||
default:
|
||||
p.inBodyEndTagOther(p.tok.DataAtom)
|
||||
}
|
||||
@@ -1030,6 +1149,21 @@ func inBodyIM(p *parser) bool {
|
||||
Type: CommentNode,
|
||||
Data: p.tok.Data,
|
||||
})
|
||||
case ErrorToken:
|
||||
// TODO: remove this divergence from the HTML5 spec.
|
||||
if len(p.templateStack) > 0 {
|
||||
p.im = inTemplateIM
|
||||
return false
|
||||
} else {
|
||||
for _, e := range p.oe {
|
||||
switch e.DataAtom {
|
||||
case a.Dd, a.Dt, a.Li, a.Optgroup, a.Option, a.P, a.Rb, a.Rp, a.Rt, a.Rtc, a.Tbody, a.Td, a.Tfoot, a.Th,
|
||||
a.Thead, a.Tr, a.Body, a.Html:
|
||||
default:
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
@@ -1135,6 +1269,12 @@ func (p *parser) inBodyEndTagFormatting(tagAtom a.Atom) {
|
||||
switch commonAncestor.DataAtom {
|
||||
case a.Table, a.Tbody, a.Tfoot, a.Thead, a.Tr:
|
||||
p.fosterParent(lastNode)
|
||||
case a.Template:
|
||||
// TODO: remove namespace checking
|
||||
if commonAncestor.Namespace == "html" {
|
||||
commonAncestor = commonAncestor.LastChild
|
||||
}
|
||||
fallthrough
|
||||
default:
|
||||
commonAncestor.AppendChild(lastNode)
|
||||
}
|
||||
@@ -1249,7 +1389,7 @@ func inTableIM(p *parser) bool {
|
||||
}
|
||||
// Ignore the token.
|
||||
return true
|
||||
case a.Style, a.Script:
|
||||
case a.Style, a.Script, a.Template:
|
||||
return inHeadIM(p)
|
||||
case a.Input:
|
||||
for _, t := range p.tok.Attr {
|
||||
@@ -1261,7 +1401,7 @@ func inTableIM(p *parser) bool {
|
||||
}
|
||||
// Otherwise drop down to the default action.
|
||||
case a.Form:
|
||||
if p.form != nil {
|
||||
if p.oe.contains(a.Template) || p.form != nil {
|
||||
// Ignore the token.
|
||||
return true
|
||||
}
|
||||
@@ -1291,6 +1431,8 @@ func inTableIM(p *parser) bool {
|
||||
case a.Body, a.Caption, a.Col, a.Colgroup, a.Html, a.Tbody, a.Td, a.Tfoot, a.Th, a.Thead, a.Tr:
|
||||
// Ignore the token.
|
||||
return true
|
||||
case a.Template:
|
||||
return inHeadIM(p)
|
||||
}
|
||||
case CommentToken:
|
||||
p.addChild(&Node{
|
||||
@@ -1386,11 +1528,13 @@ func inColumnGroupIM(p *parser) bool {
|
||||
p.oe.pop()
|
||||
p.acknowledgeSelfClosingTag()
|
||||
return true
|
||||
case a.Template:
|
||||
return inHeadIM(p)
|
||||
}
|
||||
case EndTagToken:
|
||||
switch p.tok.DataAtom {
|
||||
case a.Colgroup:
|
||||
if p.oe.top().DataAtom != a.Html {
|
||||
if p.oe.top().DataAtom == a.Colgroup {
|
||||
p.oe.pop()
|
||||
p.im = inTableIM
|
||||
}
|
||||
@@ -1398,14 +1542,16 @@ func inColumnGroupIM(p *parser) bool {
|
||||
case a.Col:
|
||||
// Ignore the token.
|
||||
return true
|
||||
case a.Template:
|
||||
return inHeadIM(p)
|
||||
}
|
||||
}
|
||||
if p.oe.top().DataAtom != a.Html {
|
||||
p.oe.pop()
|
||||
p.im = inTableIM
|
||||
return false
|
||||
if p.oe.top().DataAtom != a.Colgroup {
|
||||
return true
|
||||
}
|
||||
return true
|
||||
p.oe.pop()
|
||||
p.im = inTableIM
|
||||
return false
|
||||
}
|
||||
|
||||
// Section 12.2.6.4.13.
|
||||
@@ -1597,7 +1743,7 @@ func inSelectIM(p *parser) bool {
|
||||
p.tokenizer.NextIsNotRawText()
|
||||
// Ignore the token.
|
||||
return true
|
||||
case a.Script:
|
||||
case a.Script, a.Template:
|
||||
return inHeadIM(p)
|
||||
}
|
||||
case EndTagToken:
|
||||
@@ -1618,6 +1764,8 @@ func inSelectIM(p *parser) bool {
|
||||
if p.popUntil(selectScope, a.Select) {
|
||||
p.resetInsertionMode()
|
||||
}
|
||||
case a.Template:
|
||||
return inHeadIM(p)
|
||||
}
|
||||
case CommentToken:
|
||||
p.addChild(&Node{
|
||||
@@ -1650,6 +1798,61 @@ func inSelectInTableIM(p *parser) bool {
|
||||
return inSelectIM(p)
|
||||
}
|
||||
|
||||
// Section 12.2.6.4.18.
|
||||
func inTemplateIM(p *parser) bool {
|
||||
switch p.tok.Type {
|
||||
case TextToken, CommentToken, DoctypeToken:
|
||||
return inBodyIM(p)
|
||||
case StartTagToken:
|
||||
switch p.tok.DataAtom {
|
||||
case a.Base, a.Basefont, a.Bgsound, a.Link, a.Meta, a.Noframes, a.Script, a.Style, a.Template, a.Title:
|
||||
return inHeadIM(p)
|
||||
case a.Caption, a.Colgroup, a.Tbody, a.Tfoot, a.Thead:
|
||||
p.templateStack.pop()
|
||||
p.templateStack = append(p.templateStack, inTableIM)
|
||||
p.im = inTableIM
|
||||
return false
|
||||
case a.Col:
|
||||
p.templateStack.pop()
|
||||
p.templateStack = append(p.templateStack, inColumnGroupIM)
|
||||
p.im = inColumnGroupIM
|
||||
return false
|
||||
case a.Tr:
|
||||
p.templateStack.pop()
|
||||
p.templateStack = append(p.templateStack, inTableBodyIM)
|
||||
p.im = inTableBodyIM
|
||||
return false
|
||||
case a.Td, a.Th:
|
||||
p.templateStack.pop()
|
||||
p.templateStack = append(p.templateStack, inRowIM)
|
||||
p.im = inRowIM
|
||||
return false
|
||||
default:
|
||||
p.templateStack.pop()
|
||||
p.templateStack = append(p.templateStack, inBodyIM)
|
||||
p.im = inBodyIM
|
||||
return false
|
||||
}
|
||||
case EndTagToken:
|
||||
switch p.tok.DataAtom {
|
||||
case a.Template:
|
||||
return inHeadIM(p)
|
||||
default:
|
||||
// Ignore the token.
|
||||
return true
|
||||
}
|
||||
}
|
||||
if !p.oe.contains(a.Template) {
|
||||
// Ignore the token.
|
||||
return true
|
||||
}
|
||||
p.popUntil(defaultScope, a.Template)
|
||||
p.clearActiveFormattingElements()
|
||||
p.templateStack.pop()
|
||||
p.resetInsertionMode()
|
||||
return false
|
||||
}
|
||||
|
||||
// Section 12.2.6.4.19.
|
||||
func afterBodyIM(p *parser) bool {
|
||||
switch p.tok.Type {
|
||||
@@ -1720,6 +1923,11 @@ func inFramesetIM(p *parser) bool {
|
||||
p.acknowledgeSelfClosingTag()
|
||||
case a.Noframes:
|
||||
return inHeadIM(p)
|
||||
case a.Template:
|
||||
// TODO: remove this divergence from the HTML5 spec.
|
||||
//
|
||||
// See https://bugs.chromium.org/p/chromium/issues/detail?id=829668
|
||||
return inTemplateIM(p)
|
||||
}
|
||||
case EndTagToken:
|
||||
switch p.tok.DataAtom {
|
||||
@@ -2064,6 +2272,9 @@ func ParseFragment(r io.Reader, context *Node) ([]*Node, error) {
|
||||
}
|
||||
p.doc.AppendChild(root)
|
||||
p.oe = nodeStack{root}
|
||||
if context != nil && context.DataAtom == a.Template {
|
||||
p.templateStack = append(p.templateStack, inTemplateIM)
|
||||
}
|
||||
p.resetInsertionMode()
|
||||
|
||||
for n := context; n != nil; n = n.Parent {
|
||||
|
||||
+14
-2
@@ -125,6 +125,7 @@ func (a sortedAttributes) Swap(i, j int) {
|
||||
|
||||
func dumpLevel(w io.Writer, n *Node, level int) error {
|
||||
dumpIndent(w, level)
|
||||
level++
|
||||
switch n.Type {
|
||||
case ErrorNode:
|
||||
return errors.New("unexpected ErrorNode")
|
||||
@@ -140,13 +141,19 @@ func dumpLevel(w io.Writer, n *Node, level int) error {
|
||||
sort.Sort(attr)
|
||||
for _, a := range attr {
|
||||
io.WriteString(w, "\n")
|
||||
dumpIndent(w, level+1)
|
||||
dumpIndent(w, level)
|
||||
if a.Namespace != "" {
|
||||
fmt.Fprintf(w, `%s %s="%s"`, a.Namespace, a.Key, a.Val)
|
||||
} else {
|
||||
fmt.Fprintf(w, `%s="%s"`, a.Key, a.Val)
|
||||
}
|
||||
}
|
||||
if n.Namespace == "" && n.DataAtom == atom.Template {
|
||||
io.WriteString(w, "\n")
|
||||
dumpIndent(w, level)
|
||||
level++
|
||||
io.WriteString(w, "content")
|
||||
}
|
||||
case TextNode:
|
||||
fmt.Fprintf(w, `"%s"`, n.Data)
|
||||
case CommentNode:
|
||||
@@ -176,7 +183,7 @@ func dumpLevel(w io.Writer, n *Node, level int) error {
|
||||
}
|
||||
io.WriteString(w, "\n")
|
||||
for c := n.FirstChild; c != nil; c = c.NextSibling {
|
||||
if err := dumpLevel(w, c, level+1); err != nil {
|
||||
if err := dumpLevel(w, c, level); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
@@ -373,6 +380,11 @@ func TestNodeConsistency(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseFragmentWithNilContext(t *testing.T) {
|
||||
// This shouldn't panic.
|
||||
ParseFragment(strings.NewReader("<p>hello</p>"), nil)
|
||||
}
|
||||
|
||||
func BenchmarkParser(b *testing.B) {
|
||||
buf, err := ioutil.ReadFile("testdata/go1.html")
|
||||
if err != nil {
|
||||
|
||||
+298
@@ -0,0 +1,298 @@
|
||||
#data
|
||||
<html><ruby>a<rb>b<rb></ruby></html>
|
||||
#errors
|
||||
(1,6): expected-doctype-but-got-start-tag
|
||||
#document
|
||||
| <html>
|
||||
| <head>
|
||||
| <body>
|
||||
| <ruby>
|
||||
| "a"
|
||||
| <rb>
|
||||
| "b"
|
||||
| <rb>
|
||||
|
||||
#data
|
||||
<html><ruby>a<rb>b<rt></ruby></html>
|
||||
#errors
|
||||
(1,6): expected-doctype-but-got-start-tag
|
||||
#document
|
||||
| <html>
|
||||
| <head>
|
||||
| <body>
|
||||
| <ruby>
|
||||
| "a"
|
||||
| <rb>
|
||||
| "b"
|
||||
| <rt>
|
||||
|
||||
#data
|
||||
<html><ruby>a<rb>b<rtc></ruby></html>
|
||||
#errors
|
||||
(1,6): expected-doctype-but-got-start-tag
|
||||
#document
|
||||
| <html>
|
||||
| <head>
|
||||
| <body>
|
||||
| <ruby>
|
||||
| "a"
|
||||
| <rb>
|
||||
| "b"
|
||||
| <rtc>
|
||||
|
||||
#data
|
||||
<html><ruby>a<rb>b<rp></ruby></html>
|
||||
#errors
|
||||
(1,6): expected-doctype-but-got-start-tag
|
||||
#document
|
||||
| <html>
|
||||
| <head>
|
||||
| <body>
|
||||
| <ruby>
|
||||
| "a"
|
||||
| <rb>
|
||||
| "b"
|
||||
| <rp>
|
||||
|
||||
#data
|
||||
<html><ruby>a<rb>b<span></ruby></html>
|
||||
#errors
|
||||
(1,6): expected-doctype-but-got-start-tag
|
||||
#document
|
||||
| <html>
|
||||
| <head>
|
||||
| <body>
|
||||
| <ruby>
|
||||
| "a"
|
||||
| <rb>
|
||||
| "b"
|
||||
| <span>
|
||||
|
||||
#data
|
||||
<html><ruby>a<rt>b<rb></ruby></html>
|
||||
#errors
|
||||
(1,6): expected-doctype-but-got-start-tag
|
||||
#document
|
||||
| <html>
|
||||
| <head>
|
||||
| <body>
|
||||
| <ruby>
|
||||
| "a"
|
||||
| <rt>
|
||||
| "b"
|
||||
| <rb>
|
||||
|
||||
#data
|
||||
<html><ruby>a<rt>b<rt></ruby></html>
|
||||
#errors
|
||||
(1,6): expected-doctype-but-got-start-tag
|
||||
#document
|
||||
| <html>
|
||||
| <head>
|
||||
| <body>
|
||||
| <ruby>
|
||||
| "a"
|
||||
| <rt>
|
||||
| "b"
|
||||
| <rt>
|
||||
|
||||
#data
|
||||
<html><ruby>a<rt>b<rtc></ruby></html>
|
||||
#errors
|
||||
(1,6): expected-doctype-but-got-start-tag
|
||||
#document
|
||||
| <html>
|
||||
| <head>
|
||||
| <body>
|
||||
| <ruby>
|
||||
| "a"
|
||||
| <rt>
|
||||
| "b"
|
||||
| <rtc>
|
||||
|
||||
#data
|
||||
<html><ruby>a<rt>b<rp></ruby></html>
|
||||
#errors
|
||||
(1,6): expected-doctype-but-got-start-tag
|
||||
#document
|
||||
| <html>
|
||||
| <head>
|
||||
| <body>
|
||||
| <ruby>
|
||||
| "a"
|
||||
| <rt>
|
||||
| "b"
|
||||
| <rp>
|
||||
|
||||
#data
|
||||
<html><ruby>a<rt>b<span></ruby></html>
|
||||
#errors
|
||||
(1,6): expected-doctype-but-got-start-tag
|
||||
#document
|
||||
| <html>
|
||||
| <head>
|
||||
| <body>
|
||||
| <ruby>
|
||||
| "a"
|
||||
| <rt>
|
||||
| "b"
|
||||
| <span>
|
||||
|
||||
#data
|
||||
<html><ruby>a<rtc>b<rb></ruby></html>
|
||||
#errors
|
||||
(1,6): expected-doctype-but-got-start-tag
|
||||
#document
|
||||
| <html>
|
||||
| <head>
|
||||
| <body>
|
||||
| <ruby>
|
||||
| "a"
|
||||
| <rtc>
|
||||
| "b"
|
||||
| <rb>
|
||||
|
||||
#data
|
||||
<html><ruby>a<rtc>b<rt>c<rt>d</ruby></html>
|
||||
#errors
|
||||
(1,6): expected-doctype-but-got-start-tag
|
||||
#document
|
||||
| <html>
|
||||
| <head>
|
||||
| <body>
|
||||
| <ruby>
|
||||
| "a"
|
||||
| <rtc>
|
||||
| "b"
|
||||
| <rt>
|
||||
| "c"
|
||||
| <rt>
|
||||
| "d"
|
||||
|
||||
#data
|
||||
<html><ruby>a<rtc>b<rtc></ruby></html>
|
||||
#errors
|
||||
(1,6): expected-doctype-but-got-start-tag
|
||||
#document
|
||||
| <html>
|
||||
| <head>
|
||||
| <body>
|
||||
| <ruby>
|
||||
| "a"
|
||||
| <rtc>
|
||||
| "b"
|
||||
| <rtc>
|
||||
|
||||
#data
|
||||
<html><ruby>a<rtc>b<rp></ruby></html>
|
||||
#errors
|
||||
(1,6): expected-doctype-but-got-start-tag
|
||||
#document
|
||||
| <html>
|
||||
| <head>
|
||||
| <body>
|
||||
| <ruby>
|
||||
| "a"
|
||||
| <rtc>
|
||||
| "b"
|
||||
| <rp>
|
||||
|
||||
#data
|
||||
<html><ruby>a<rtc>b<span></ruby></html>
|
||||
#errors
|
||||
(1,6): expected-doctype-but-got-start-tag
|
||||
#document
|
||||
| <html>
|
||||
| <head>
|
||||
| <body>
|
||||
| <ruby>
|
||||
| "a"
|
||||
| <rtc>
|
||||
| "b"
|
||||
| <span>
|
||||
|
||||
#data
|
||||
<html><ruby>a<rp>b<rb></ruby></html>
|
||||
#errors
|
||||
(1,6): expected-doctype-but-got-start-tag
|
||||
#document
|
||||
| <html>
|
||||
| <head>
|
||||
| <body>
|
||||
| <ruby>
|
||||
| "a"
|
||||
| <rp>
|
||||
| "b"
|
||||
| <rb>
|
||||
|
||||
#data
|
||||
<html><ruby>a<rp>b<rt></ruby></html>
|
||||
#errors
|
||||
(1,6): expected-doctype-but-got-start-tag
|
||||
#document
|
||||
| <html>
|
||||
| <head>
|
||||
| <body>
|
||||
| <ruby>
|
||||
| "a"
|
||||
| <rp>
|
||||
| "b"
|
||||
| <rt>
|
||||
|
||||
#data
|
||||
<html><ruby>a<rp>b<rtc></ruby></html>
|
||||
#errors
|
||||
(1,6): expected-doctype-but-got-start-tag
|
||||
#document
|
||||
| <html>
|
||||
| <head>
|
||||
| <body>
|
||||
| <ruby>
|
||||
| "a"
|
||||
| <rp>
|
||||
| "b"
|
||||
| <rtc>
|
||||
|
||||
#data
|
||||
<html><ruby>a<rp>b<rp></ruby></html>
|
||||
#errors
|
||||
(1,6): expected-doctype-but-got-start-tag
|
||||
#document
|
||||
| <html>
|
||||
| <head>
|
||||
| <body>
|
||||
| <ruby>
|
||||
| "a"
|
||||
| <rp>
|
||||
| "b"
|
||||
| <rp>
|
||||
|
||||
#data
|
||||
<html><ruby>a<rp>b<span></ruby></html>
|
||||
#errors
|
||||
(1,6): expected-doctype-but-got-start-tag
|
||||
#document
|
||||
| <html>
|
||||
| <head>
|
||||
| <body>
|
||||
| <ruby>
|
||||
| "a"
|
||||
| <rp>
|
||||
| "b"
|
||||
| <span>
|
||||
|
||||
#data
|
||||
<html><ruby><rtc><ruby>a<rb>b<rt></ruby></ruby></html>
|
||||
#errors
|
||||
(1,6): expected-doctype-but-got-start-tag
|
||||
#document
|
||||
| <html>
|
||||
| <head>
|
||||
| <body>
|
||||
| <ruby>
|
||||
| <rtc>
|
||||
| <ruby>
|
||||
| "a"
|
||||
| <rb>
|
||||
| "b"
|
||||
| <rt>
|
||||
+1117
File diff suppressed because it is too large
Load Diff
+50
@@ -0,0 +1,50 @@
|
||||
// Copyright 2018 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Package httpguts provides functions implementing various details
|
||||
// of the HTTP specification.
|
||||
//
|
||||
// This package is shared by the standard library (which vendors it)
|
||||
// and x/net/http2. It comes with no API stability promise.
|
||||
package httpguts
|
||||
|
||||
import (
|
||||
"net/textproto"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// ValidTrailerHeader reports whether name is a valid header field name to appear
|
||||
// in trailers.
|
||||
// See RFC 7230, Section 4.1.2
|
||||
func ValidTrailerHeader(name string) bool {
|
||||
name = textproto.CanonicalMIMEHeaderKey(name)
|
||||
if strings.HasPrefix(name, "If-") || badTrailer[name] {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
var badTrailer = map[string]bool{
|
||||
"Authorization": true,
|
||||
"Cache-Control": true,
|
||||
"Connection": true,
|
||||
"Content-Encoding": true,
|
||||
"Content-Length": true,
|
||||
"Content-Range": true,
|
||||
"Content-Type": true,
|
||||
"Expect": true,
|
||||
"Host": true,
|
||||
"Keep-Alive": true,
|
||||
"Max-Forwards": true,
|
||||
"Pragma": true,
|
||||
"Proxy-Authenticate": true,
|
||||
"Proxy-Authorization": true,
|
||||
"Proxy-Connection": true,
|
||||
"Range": true,
|
||||
"Realm": true,
|
||||
"Te": true,
|
||||
"Trailer": true,
|
||||
"Transfer-Encoding": true,
|
||||
"Www-Authenticate": true,
|
||||
}
|
||||
+12
-38
@@ -46,6 +46,7 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"golang.org/x/net/http/httpguts"
|
||||
"golang.org/x/net/http2/hpack"
|
||||
)
|
||||
|
||||
@@ -1817,7 +1818,7 @@ func (st *stream) processTrailerHeaders(f *MetaHeadersFrame) error {
|
||||
if st.trailer != nil {
|
||||
for _, hf := range f.RegularFields() {
|
||||
key := sc.canonicalHeader(hf.Name)
|
||||
if !ValidTrailerHeader(key) {
|
||||
if !httpguts.ValidTrailerHeader(key) {
|
||||
// TODO: send more details to the peer somehow. But http2 has
|
||||
// no way to send debug data at a stream level. Discuss with
|
||||
// HTTP folk.
|
||||
@@ -2284,7 +2285,7 @@ func (rws *responseWriterState) hasTrailers() bool { return len(rws.trailers) !=
|
||||
// written in the trailers at the end of the response.
|
||||
func (rws *responseWriterState) declareTrailer(k string) {
|
||||
k = http.CanonicalHeaderKey(k)
|
||||
if !ValidTrailerHeader(k) {
|
||||
if !httpguts.ValidTrailerHeader(k) {
|
||||
// Forbidden by RFC 7230, section 4.1.2.
|
||||
rws.conn.logf("ignoring invalid trailer %q", k)
|
||||
return
|
||||
@@ -2323,7 +2324,15 @@ func (rws *responseWriterState) writeChunk(p []byte) (n int, err error) {
|
||||
}
|
||||
_, hasContentType := rws.snapHeader["Content-Type"]
|
||||
if !hasContentType && bodyAllowedForStatus(rws.status) && len(p) > 0 {
|
||||
ctype = http.DetectContentType(p)
|
||||
if cto := rws.snapHeader.Get("X-Content-Type-Options"); strings.EqualFold("nosniff", cto) {
|
||||
// nosniff is an explicit directive not to guess a content-type.
|
||||
// Content-sniffing is no less susceptible to polyglot attacks via
|
||||
// hosted content when done on the server.
|
||||
ctype = "application/octet-stream"
|
||||
rws.conn.logf("http2: WriteHeader called with X-Content-Type-Options:nosniff but no Content-Type")
|
||||
} else {
|
||||
ctype = http.DetectContentType(p)
|
||||
}
|
||||
}
|
||||
var date string
|
||||
if _, ok := rws.snapHeader["Date"]; !ok {
|
||||
@@ -2838,41 +2847,6 @@ func new400Handler(err error) http.HandlerFunc {
|
||||
}
|
||||
}
|
||||
|
||||
// ValidTrailerHeader reports whether name is a valid header field name to appear
|
||||
// in trailers.
|
||||
// See: http://tools.ietf.org/html/rfc7230#section-4.1.2
|
||||
func ValidTrailerHeader(name string) bool {
|
||||
name = http.CanonicalHeaderKey(name)
|
||||
if strings.HasPrefix(name, "If-") || badTrailer[name] {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
var badTrailer = map[string]bool{
|
||||
"Authorization": true,
|
||||
"Cache-Control": true,
|
||||
"Connection": true,
|
||||
"Content-Encoding": true,
|
||||
"Content-Length": true,
|
||||
"Content-Range": true,
|
||||
"Content-Type": true,
|
||||
"Expect": true,
|
||||
"Host": true,
|
||||
"Keep-Alive": true,
|
||||
"Max-Forwards": true,
|
||||
"Pragma": true,
|
||||
"Proxy-Authenticate": true,
|
||||
"Proxy-Authorization": true,
|
||||
"Proxy-Connection": true,
|
||||
"Range": true,
|
||||
"Realm": true,
|
||||
"Te": true,
|
||||
"Trailer": true,
|
||||
"Transfer-Encoding": true,
|
||||
"Www-Authenticate": true,
|
||||
}
|
||||
|
||||
// h1ServerKeepAlivesDisabled reports whether hs has its keep-alives
|
||||
// disabled. See comments on h1ServerShutdownChan above for why
|
||||
// the code is written this way.
|
||||
|
||||
+36
@@ -1760,6 +1760,42 @@ func TestServer_Response_Data_Sniff_DoesntOverride(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func TestServer_Response_Nosniff_WithoutContentType(t *testing.T) {
|
||||
const msg = "<html>this is HTML."
|
||||
testServerResponse(t, func(w http.ResponseWriter, r *http.Request) error {
|
||||
w.Header().Set("X-Content-Type-Options", "nosniff")
|
||||
w.WriteHeader(200)
|
||||
io.WriteString(w, msg)
|
||||
return nil
|
||||
}, func(st *serverTester) {
|
||||
getSlash(st)
|
||||
hf := st.wantHeaders()
|
||||
if hf.StreamEnded() {
|
||||
t.Fatal("don't want END_STREAM, expecting data")
|
||||
}
|
||||
if !hf.HeadersEnded() {
|
||||
t.Fatal("want END_HEADERS flag")
|
||||
}
|
||||
goth := st.decodeHeader(hf.HeaderBlockFragment())
|
||||
wanth := [][2]string{
|
||||
{":status", "200"},
|
||||
{"x-content-type-options", "nosniff"},
|
||||
{"content-type", "application/octet-stream"},
|
||||
{"content-length", strconv.Itoa(len(msg))},
|
||||
}
|
||||
if !reflect.DeepEqual(goth, wanth) {
|
||||
t.Errorf("Got headers %v; want %v", goth, wanth)
|
||||
}
|
||||
df := st.wantData()
|
||||
if !df.StreamEnded() {
|
||||
t.Error("expected DATA to have END_STREAM flag")
|
||||
}
|
||||
if got := string(df.Data()); got != msg {
|
||||
t.Errorf("got DATA %q; want %q", got, msg)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestServer_Response_TransferEncoding_chunked(t *testing.T) {
|
||||
const msg = "hi"
|
||||
testServerResponse(t, func(w http.ResponseWriter, r *http.Request) error {
|
||||
|
||||
+3
@@ -951,6 +951,9 @@ func (cc *ClientConn) awaitOpenSlotForRequest(req *http.Request) error {
|
||||
for {
|
||||
cc.lastActive = time.Now()
|
||||
if cc.closed || !cc.canTakeNewRequestLocked() {
|
||||
if waitingForConn != nil {
|
||||
close(waitingForConn)
|
||||
}
|
||||
return errClientConnUnusable
|
||||
}
|
||||
if int64(len(cc.streams))+1 <= int64(cc.maxConcurrentStreams) {
|
||||
|
||||
+53
-3
@@ -71,8 +71,7 @@ func TestDiag(t *testing.T) {
|
||||
})
|
||||
t.Run("Ping/Privileged", func(t *testing.T) {
|
||||
if m, ok := nettest.SupportsRawIPSocket(); !ok {
|
||||
t.Log(m)
|
||||
return
|
||||
t.Skip(m)
|
||||
}
|
||||
for i, dt := range []diagTest{
|
||||
{
|
||||
@@ -102,6 +101,50 @@ func TestDiag(t *testing.T) {
|
||||
}
|
||||
}
|
||||
})
|
||||
t.Run("Probe/Privileged", func(t *testing.T) {
|
||||
if m, ok := nettest.SupportsRawIPSocket(); !ok {
|
||||
t.Skip(m)
|
||||
}
|
||||
for i, dt := range []diagTest{
|
||||
{
|
||||
"ip4:icmp", "0.0.0.0", iana.ProtocolICMP,
|
||||
icmp.Message{
|
||||
Type: ipv4.ICMPTypeExtendedEchoRequest, Code: 0,
|
||||
Body: &icmp.ExtendedEchoRequest{
|
||||
ID: os.Getpid() & 0xffff,
|
||||
Local: true,
|
||||
Extensions: []icmp.Extension{
|
||||
&icmp.InterfaceIdent{
|
||||
Class: 3, Type: 1,
|
||||
Name: "doesnotexist",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
"ip6:ipv6-icmp", "::", iana.ProtocolIPv6ICMP,
|
||||
icmp.Message{
|
||||
Type: ipv6.ICMPTypeExtendedEchoRequest, Code: 0,
|
||||
Body: &icmp.ExtendedEchoRequest{
|
||||
ID: os.Getpid() & 0xffff,
|
||||
Local: true,
|
||||
Extensions: []icmp.Extension{
|
||||
&icmp.InterfaceIdent{
|
||||
Class: 3, Type: 1,
|
||||
Name: "doesnotexist",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
} {
|
||||
if err := doDiag(dt, i); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func doDiag(dt diagTest, seq int) error {
|
||||
@@ -124,6 +167,7 @@ func doDiag(dt diagTest, seq int) error {
|
||||
f.Accept(ipv6.ICMPTypeTimeExceeded)
|
||||
f.Accept(ipv6.ICMPTypeParameterProblem)
|
||||
f.Accept(ipv6.ICMPTypeEchoReply)
|
||||
f.Accept(ipv6.ICMPTypeExtendedEchoReply)
|
||||
if err := c.IPv6PacketConn().SetICMPFilter(&f); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -132,6 +176,8 @@ func doDiag(dt diagTest, seq int) error {
|
||||
switch m := dt.m.Body.(type) {
|
||||
case *icmp.Echo:
|
||||
m.Seq = 1 << uint(seq)
|
||||
case *icmp.ExtendedEchoRequest:
|
||||
m.Seq = 1 << uint(seq)
|
||||
}
|
||||
wb, err := dt.m.Marshal(nil)
|
||||
if err != nil {
|
||||
@@ -159,9 +205,13 @@ func doDiag(dt diagTest, seq int) error {
|
||||
case dt.m.Type == ipv4.ICMPTypeEcho && rm.Type == ipv4.ICMPTypeEchoReply:
|
||||
fallthrough
|
||||
case dt.m.Type == ipv6.ICMPTypeEchoRequest && rm.Type == ipv6.ICMPTypeEchoReply:
|
||||
fallthrough
|
||||
case dt.m.Type == ipv4.ICMPTypeExtendedEchoRequest && rm.Type == ipv4.ICMPTypeExtendedEchoReply:
|
||||
fallthrough
|
||||
case dt.m.Type == ipv6.ICMPTypeExtendedEchoRequest && rm.Type == ipv6.ICMPTypeExtendedEchoReply:
|
||||
return nil
|
||||
default:
|
||||
return fmt.Errorf("got %+v from %v; want echo reply", rm, peer)
|
||||
return fmt.Errorf("got %+v from %v; want echo reply or extended echo reply", rm, peer)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+4
-4
@@ -16,24 +16,24 @@ func (p *DstUnreach) Len(proto int) int {
|
||||
if p == nil {
|
||||
return 0
|
||||
}
|
||||
l, _ := multipartMessageBodyDataLen(proto, p.Data, p.Extensions)
|
||||
l, _ := multipartMessageBodyDataLen(proto, true, p.Data, p.Extensions)
|
||||
return 4 + l
|
||||
}
|
||||
|
||||
// Marshal implements the Marshal method of MessageBody interface.
|
||||
func (p *DstUnreach) Marshal(proto int) ([]byte, error) {
|
||||
return marshalMultipartMessageBody(proto, p.Data, p.Extensions)
|
||||
return marshalMultipartMessageBody(proto, true, p.Data, p.Extensions)
|
||||
}
|
||||
|
||||
// parseDstUnreach parses b as an ICMP destination unreachable message
|
||||
// body.
|
||||
func parseDstUnreach(proto int, b []byte) (MessageBody, error) {
|
||||
func parseDstUnreach(proto int, typ Type, b []byte) (MessageBody, error) {
|
||||
if len(b) < 4 {
|
||||
return nil, errMessageTooShort
|
||||
}
|
||||
p := &DstUnreach{}
|
||||
var err error
|
||||
p.Data, p.Extensions, err = parseMultipartMessageBody(proto, b)
|
||||
p.Data, p.Extensions, err = parseMultipartMessageBody(proto, typ, b)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
+113
-1
@@ -31,7 +31,7 @@ func (p *Echo) Marshal(proto int) ([]byte, error) {
|
||||
}
|
||||
|
||||
// parseEcho parses b as an ICMP echo request or reply message body.
|
||||
func parseEcho(proto int, b []byte) (MessageBody, error) {
|
||||
func parseEcho(proto int, _ Type, b []byte) (MessageBody, error) {
|
||||
bodyLen := len(b)
|
||||
if bodyLen < 4 {
|
||||
return nil, errMessageTooShort
|
||||
@@ -43,3 +43,115 @@ func parseEcho(proto int, b []byte) (MessageBody, error) {
|
||||
}
|
||||
return p, nil
|
||||
}
|
||||
|
||||
// An ExtendedEchoRequest represents an ICMP extended echo request
|
||||
// message body.
|
||||
type ExtendedEchoRequest struct {
|
||||
ID int // identifier
|
||||
Seq int // sequence number
|
||||
Local bool // must be true when identifying by name or index
|
||||
Extensions []Extension // extensions
|
||||
}
|
||||
|
||||
// Len implements the Len method of MessageBody interface.
|
||||
func (p *ExtendedEchoRequest) Len(proto int) int {
|
||||
if p == nil {
|
||||
return 0
|
||||
}
|
||||
l, _ := multipartMessageBodyDataLen(proto, false, nil, p.Extensions)
|
||||
return 4 + l
|
||||
}
|
||||
|
||||
// Marshal implements the Marshal method of MessageBody interface.
|
||||
func (p *ExtendedEchoRequest) Marshal(proto int) ([]byte, error) {
|
||||
b, err := marshalMultipartMessageBody(proto, false, nil, p.Extensions)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
bb := make([]byte, 4)
|
||||
binary.BigEndian.PutUint16(bb[:2], uint16(p.ID))
|
||||
bb[2] = byte(p.Seq)
|
||||
if p.Local {
|
||||
bb[3] |= 0x01
|
||||
}
|
||||
bb = append(bb, b...)
|
||||
return bb, nil
|
||||
}
|
||||
|
||||
// parseExtendedEchoRequest parses b as an ICMP extended echo request
|
||||
// message body.
|
||||
func parseExtendedEchoRequest(proto int, typ Type, b []byte) (MessageBody, error) {
|
||||
if len(b) < 4+4 {
|
||||
return nil, errMessageTooShort
|
||||
}
|
||||
p := &ExtendedEchoRequest{ID: int(binary.BigEndian.Uint16(b[:2])), Seq: int(b[2])}
|
||||
if b[3]&0x01 != 0 {
|
||||
p.Local = true
|
||||
}
|
||||
var err error
|
||||
_, p.Extensions, err = parseMultipartMessageBody(proto, typ, b[4:])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return p, nil
|
||||
}
|
||||
|
||||
// An ExtendedEchoReply represents an ICMP extended echo reply message
|
||||
// body.
|
||||
type ExtendedEchoReply struct {
|
||||
ID int // identifier
|
||||
Seq int // sequence number
|
||||
State int // 3-bit state working together with Message.Code
|
||||
Active bool // probed interface is active
|
||||
IPv4 bool // probed interface runs IPv4
|
||||
IPv6 bool // probed interface runs IPv6
|
||||
}
|
||||
|
||||
// Len implements the Len method of MessageBody interface.
|
||||
func (p *ExtendedEchoReply) Len(proto int) int {
|
||||
if p == nil {
|
||||
return 0
|
||||
}
|
||||
return 4
|
||||
}
|
||||
|
||||
// Marshal implements the Marshal method of MessageBody interface.
|
||||
func (p *ExtendedEchoReply) Marshal(proto int) ([]byte, error) {
|
||||
b := make([]byte, 4)
|
||||
binary.BigEndian.PutUint16(b[:2], uint16(p.ID))
|
||||
b[2] = byte(p.Seq)
|
||||
b[3] = byte(p.State<<5) & 0xe0
|
||||
if p.Active {
|
||||
b[3] |= 0x04
|
||||
}
|
||||
if p.IPv4 {
|
||||
b[3] |= 0x02
|
||||
}
|
||||
if p.IPv6 {
|
||||
b[3] |= 0x01
|
||||
}
|
||||
return b, nil
|
||||
}
|
||||
|
||||
// parseExtendedEchoReply parses b as an ICMP extended echo reply
|
||||
// message body.
|
||||
func parseExtendedEchoReply(proto int, _ Type, b []byte) (MessageBody, error) {
|
||||
if len(b) < 4 {
|
||||
return nil, errMessageTooShort
|
||||
}
|
||||
p := &ExtendedEchoReply{
|
||||
ID: int(binary.BigEndian.Uint16(b[:2])),
|
||||
Seq: int(b[2]),
|
||||
State: int(b[3]) >> 5,
|
||||
}
|
||||
if b[3]&0x04 != 0 {
|
||||
p.Active = true
|
||||
}
|
||||
if b[3]&0x02 != 0 {
|
||||
p.IPv4 = true
|
||||
}
|
||||
if b[3]&0x01 != 0 {
|
||||
p.IPv6 = true
|
||||
}
|
||||
return p, nil
|
||||
}
|
||||
|
||||
+31
-12
@@ -4,7 +4,12 @@
|
||||
|
||||
package icmp
|
||||
|
||||
import "encoding/binary"
|
||||
import (
|
||||
"encoding/binary"
|
||||
|
||||
"golang.org/x/net/ipv4"
|
||||
"golang.org/x/net/ipv6"
|
||||
)
|
||||
|
||||
// An Extension represents an ICMP extension.
|
||||
type Extension interface {
|
||||
@@ -38,7 +43,7 @@ func validExtensionHeader(b []byte) bool {
|
||||
// It will return a list of ICMP extensions and an adjusted length
|
||||
// attribute that represents the length of the padded original
|
||||
// datagram field. Otherwise, it returns an error.
|
||||
func parseExtensions(b []byte, l int) ([]Extension, int, error) {
|
||||
func parseExtensions(typ Type, b []byte, l int) ([]Extension, int, error) {
|
||||
// Still a lot of non-RFC 4884 compliant implementations are
|
||||
// out there. Set the length attribute l to 128 when it looks
|
||||
// inappropriate for backwards compatibility.
|
||||
@@ -48,20 +53,28 @@ func parseExtensions(b []byte, l int) ([]Extension, int, error) {
|
||||
// header.
|
||||
//
|
||||
// See RFC 4884 for further information.
|
||||
if 128 > l || l+8 > len(b) {
|
||||
l = 128
|
||||
}
|
||||
if l+8 > len(b) {
|
||||
return nil, -1, errNoExtension
|
||||
}
|
||||
if !validExtensionHeader(b[l:]) {
|
||||
if l == 128 {
|
||||
switch typ {
|
||||
case ipv4.ICMPTypeExtendedEchoRequest, ipv6.ICMPTypeExtendedEchoRequest:
|
||||
if len(b) < 8 || !validExtensionHeader(b) {
|
||||
return nil, -1, errNoExtension
|
||||
}
|
||||
l = 128
|
||||
if !validExtensionHeader(b[l:]) {
|
||||
l = 0
|
||||
default:
|
||||
if 128 > l || l+8 > len(b) {
|
||||
l = 128
|
||||
}
|
||||
if l+8 > len(b) {
|
||||
return nil, -1, errNoExtension
|
||||
}
|
||||
if !validExtensionHeader(b[l:]) {
|
||||
if l == 128 {
|
||||
return nil, -1, errNoExtension
|
||||
}
|
||||
l = 128
|
||||
if !validExtensionHeader(b[l:]) {
|
||||
return nil, -1, errNoExtension
|
||||
}
|
||||
}
|
||||
}
|
||||
var exts []Extension
|
||||
for b = b[l+4:]; len(b) >= 4; {
|
||||
@@ -82,6 +95,12 @@ func parseExtensions(b []byte, l int) ([]Extension, int, error) {
|
||||
return nil, -1, err
|
||||
}
|
||||
exts = append(exts, ext)
|
||||
case classInterfaceIdent:
|
||||
ext, err := parseInterfaceIdent(b[:ol])
|
||||
if err != nil {
|
||||
return nil, -1, err
|
||||
}
|
||||
exts = append(exts, ext)
|
||||
}
|
||||
b = b[ol:]
|
||||
}
|
||||
|
||||
+127
-33
@@ -11,10 +11,12 @@ import (
|
||||
"testing"
|
||||
|
||||
"golang.org/x/net/internal/iana"
|
||||
"golang.org/x/net/ipv4"
|
||||
"golang.org/x/net/ipv6"
|
||||
)
|
||||
|
||||
func TestMarshalAndParseExtension(t *testing.T) {
|
||||
fn := func(t *testing.T, proto int, hdr, obj []byte, te Extension) error {
|
||||
fn := func(t *testing.T, proto int, typ Type, hdr, obj []byte, te Extension) error {
|
||||
b, err := te.Marshal(proto)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -22,39 +24,53 @@ func TestMarshalAndParseExtension(t *testing.T) {
|
||||
if !reflect.DeepEqual(b, obj) {
|
||||
return fmt.Errorf("got %#v; want %#v", b, obj)
|
||||
}
|
||||
for i, wire := range []struct {
|
||||
data []byte // original datagram
|
||||
inlattr int // length of padded original datagram, a hint
|
||||
outlattr int // length of padded original datagram, a want
|
||||
err error
|
||||
}{
|
||||
{nil, 0, -1, errNoExtension},
|
||||
{make([]byte, 127), 128, -1, errNoExtension},
|
||||
|
||||
{make([]byte, 128), 127, -1, errNoExtension},
|
||||
{make([]byte, 128), 128, -1, errNoExtension},
|
||||
{make([]byte, 128), 129, -1, errNoExtension},
|
||||
|
||||
{append(make([]byte, 128), append(hdr, obj...)...), 127, 128, nil},
|
||||
{append(make([]byte, 128), append(hdr, obj...)...), 128, 128, nil},
|
||||
{append(make([]byte, 128), append(hdr, obj...)...), 129, 128, nil},
|
||||
|
||||
{append(make([]byte, 512), append(hdr, obj...)...), 511, -1, errNoExtension},
|
||||
{append(make([]byte, 512), append(hdr, obj...)...), 512, 512, nil},
|
||||
{append(make([]byte, 512), append(hdr, obj...)...), 513, -1, errNoExtension},
|
||||
} {
|
||||
exts, l, err := parseExtensions(wire.data, wire.inlattr)
|
||||
if err != wire.err {
|
||||
return fmt.Errorf("#%d: got %v; want %v", i, err, wire.err)
|
||||
switch typ {
|
||||
case ipv4.ICMPTypeExtendedEchoRequest, ipv6.ICMPTypeExtendedEchoRequest:
|
||||
exts, l, err := parseExtensions(typ, append(hdr, obj...), 0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if wire.err != nil {
|
||||
continue
|
||||
}
|
||||
if l != wire.outlattr {
|
||||
return fmt.Errorf("#%d: got %d; want %d", i, l, wire.outlattr)
|
||||
if l != 0 {
|
||||
return fmt.Errorf("got %d; want 0", l)
|
||||
}
|
||||
if !reflect.DeepEqual(exts, []Extension{te}) {
|
||||
return fmt.Errorf("#%d: got %#v; want %#v", i, exts[0], te)
|
||||
return fmt.Errorf("got %#v; want %#v", exts[0], te)
|
||||
}
|
||||
default:
|
||||
for i, wire := range []struct {
|
||||
data []byte // original datagram
|
||||
inlattr int // length of padded original datagram, a hint
|
||||
outlattr int // length of padded original datagram, a want
|
||||
err error
|
||||
}{
|
||||
{nil, 0, -1, errNoExtension},
|
||||
{make([]byte, 127), 128, -1, errNoExtension},
|
||||
|
||||
{make([]byte, 128), 127, -1, errNoExtension},
|
||||
{make([]byte, 128), 128, -1, errNoExtension},
|
||||
{make([]byte, 128), 129, -1, errNoExtension},
|
||||
|
||||
{append(make([]byte, 128), append(hdr, obj...)...), 127, 128, nil},
|
||||
{append(make([]byte, 128), append(hdr, obj...)...), 128, 128, nil},
|
||||
{append(make([]byte, 128), append(hdr, obj...)...), 129, 128, nil},
|
||||
|
||||
{append(make([]byte, 512), append(hdr, obj...)...), 511, -1, errNoExtension},
|
||||
{append(make([]byte, 512), append(hdr, obj...)...), 512, 512, nil},
|
||||
{append(make([]byte, 512), append(hdr, obj...)...), 513, -1, errNoExtension},
|
||||
} {
|
||||
exts, l, err := parseExtensions(typ, wire.data, wire.inlattr)
|
||||
if err != wire.err {
|
||||
return fmt.Errorf("#%d: got %v; want %v", i, err, wire.err)
|
||||
}
|
||||
if wire.err != nil {
|
||||
continue
|
||||
}
|
||||
if l != wire.outlattr {
|
||||
return fmt.Errorf("#%d: got %d; want %d", i, l, wire.outlattr)
|
||||
}
|
||||
if !reflect.DeepEqual(exts, []Extension{te}) {
|
||||
return fmt.Errorf("#%d: got %#v; want %#v", i, exts[0], te)
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
@@ -63,6 +79,7 @@ func TestMarshalAndParseExtension(t *testing.T) {
|
||||
t.Run("MPLSLabelStack", func(t *testing.T) {
|
||||
for _, et := range []struct {
|
||||
proto int
|
||||
typ Type
|
||||
hdr []byte
|
||||
obj []byte
|
||||
ext Extension
|
||||
@@ -70,6 +87,7 @@ func TestMarshalAndParseExtension(t *testing.T) {
|
||||
// MPLS label stack with no label
|
||||
{
|
||||
proto: iana.ProtocolICMP,
|
||||
typ: ipv4.ICMPTypeDestinationUnreachable,
|
||||
hdr: []byte{
|
||||
0x20, 0x00, 0x00, 0x00,
|
||||
},
|
||||
@@ -84,6 +102,7 @@ func TestMarshalAndParseExtension(t *testing.T) {
|
||||
// MPLS label stack with a single label
|
||||
{
|
||||
proto: iana.ProtocolIPv6ICMP,
|
||||
typ: ipv6.ICMPTypeDestinationUnreachable,
|
||||
hdr: []byte{
|
||||
0x20, 0x00, 0x00, 0x00,
|
||||
},
|
||||
@@ -107,6 +126,7 @@ func TestMarshalAndParseExtension(t *testing.T) {
|
||||
// MPLS label stack with multiple labels
|
||||
{
|
||||
proto: iana.ProtocolICMP,
|
||||
typ: ipv4.ICMPTypeDestinationUnreachable,
|
||||
hdr: []byte{
|
||||
0x20, 0x00, 0x00, 0x00,
|
||||
},
|
||||
@@ -135,7 +155,7 @@ func TestMarshalAndParseExtension(t *testing.T) {
|
||||
},
|
||||
},
|
||||
} {
|
||||
if err := fn(t, et.proto, et.hdr, et.obj, et.ext); err != nil {
|
||||
if err := fn(t, et.proto, et.typ, et.hdr, et.obj, et.ext); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
@@ -143,6 +163,7 @@ func TestMarshalAndParseExtension(t *testing.T) {
|
||||
t.Run("InterfaceInfo", func(t *testing.T) {
|
||||
for _, et := range []struct {
|
||||
proto int
|
||||
typ Type
|
||||
hdr []byte
|
||||
obj []byte
|
||||
ext Extension
|
||||
@@ -150,6 +171,7 @@ func TestMarshalAndParseExtension(t *testing.T) {
|
||||
// Interface information with no attribute
|
||||
{
|
||||
proto: iana.ProtocolICMP,
|
||||
typ: ipv4.ICMPTypeDestinationUnreachable,
|
||||
hdr: []byte{
|
||||
0x20, 0x00, 0x00, 0x00,
|
||||
},
|
||||
@@ -163,6 +185,7 @@ func TestMarshalAndParseExtension(t *testing.T) {
|
||||
// Interface information with ifIndex and name
|
||||
{
|
||||
proto: iana.ProtocolICMP,
|
||||
typ: ipv4.ICMPTypeDestinationUnreachable,
|
||||
hdr: []byte{
|
||||
0x20, 0x00, 0x00, 0x00,
|
||||
},
|
||||
@@ -184,6 +207,7 @@ func TestMarshalAndParseExtension(t *testing.T) {
|
||||
// Interface information with ifIndex, IPAddr, name and MTU
|
||||
{
|
||||
proto: iana.ProtocolIPv6ICMP,
|
||||
typ: ipv6.ICMPTypeDestinationUnreachable,
|
||||
hdr: []byte{
|
||||
0x20, 0x00, 0x00, 0x00,
|
||||
},
|
||||
@@ -214,7 +238,77 @@ func TestMarshalAndParseExtension(t *testing.T) {
|
||||
},
|
||||
},
|
||||
} {
|
||||
if err := fn(t, et.proto, et.hdr, et.obj, et.ext); err != nil {
|
||||
if err := fn(t, et.proto, et.typ, et.hdr, et.obj, et.ext); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
})
|
||||
t.Run("InterfaceIdent", func(t *testing.T) {
|
||||
for _, et := range []struct {
|
||||
proto int
|
||||
typ Type
|
||||
hdr []byte
|
||||
obj []byte
|
||||
ext Extension
|
||||
}{
|
||||
// Interface identification by name
|
||||
{
|
||||
proto: iana.ProtocolICMP,
|
||||
typ: ipv4.ICMPTypeExtendedEchoRequest,
|
||||
hdr: []byte{
|
||||
0x20, 0x00, 0x00, 0x00,
|
||||
},
|
||||
obj: []byte{
|
||||
0x00, 0x0c, 0x03, 0x01,
|
||||
byte('e'), byte('n'), byte('1'), byte('0'),
|
||||
byte('1'), 0x00, 0x00, 0x00,
|
||||
},
|
||||
ext: &InterfaceIdent{
|
||||
Class: classInterfaceIdent,
|
||||
Type: typeInterfaceByName,
|
||||
Name: "en101",
|
||||
},
|
||||
},
|
||||
// Interface identification by index
|
||||
{
|
||||
proto: iana.ProtocolIPv6ICMP,
|
||||
typ: ipv6.ICMPTypeExtendedEchoRequest,
|
||||
hdr: []byte{
|
||||
0x20, 0x00, 0x00, 0x00,
|
||||
},
|
||||
obj: []byte{
|
||||
0x00, 0x0c, 0x03, 0x02,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x03, 0x8f,
|
||||
},
|
||||
ext: &InterfaceIdent{
|
||||
Class: classInterfaceIdent,
|
||||
Type: typeInterfaceByIndex,
|
||||
Index: 911,
|
||||
},
|
||||
},
|
||||
// Interface identification by address
|
||||
{
|
||||
proto: iana.ProtocolICMP,
|
||||
typ: ipv4.ICMPTypeExtendedEchoRequest,
|
||||
hdr: []byte{
|
||||
0x20, 0x00, 0x00, 0x00,
|
||||
},
|
||||
obj: []byte{
|
||||
0x00, 0x10, 0x03, 0x03,
|
||||
byte(iana.AddrFamily48bitMAC >> 8), byte(iana.AddrFamily48bitMAC & 0x0f), 0x06, 0x00,
|
||||
0x01, 0x23, 0x45, 0x67,
|
||||
0x89, 0xab, 0x00, 0x00,
|
||||
},
|
||||
ext: &InterfaceIdent{
|
||||
Class: classInterfaceIdent,
|
||||
Type: typeInterfaceByAddress,
|
||||
AFI: iana.AddrFamily48bitMAC,
|
||||
Addr: []byte{0x01, 0x23, 0x45, 0x67, 0x89, 0xab},
|
||||
},
|
||||
},
|
||||
} {
|
||||
if err := fn(t, et.proto, et.typ, et.hdr, et.obj, et.ext); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
+93
-7
@@ -14,9 +14,6 @@ import (
|
||||
|
||||
const (
|
||||
classInterfaceInfo = 2
|
||||
|
||||
afiIPv4 = 1
|
||||
afiIPv6 = 2
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -127,11 +124,11 @@ func (ifi *InterfaceInfo) parseIfIndex(b []byte) ([]byte, error) {
|
||||
func (ifi *InterfaceInfo) marshalIPAddr(proto int, b []byte) []byte {
|
||||
switch proto {
|
||||
case iana.ProtocolICMP:
|
||||
binary.BigEndian.PutUint16(b[:2], uint16(afiIPv4))
|
||||
binary.BigEndian.PutUint16(b[:2], uint16(iana.AddrFamilyIPv4))
|
||||
copy(b[4:4+net.IPv4len], ifi.Addr.IP.To4())
|
||||
b = b[4+net.IPv4len:]
|
||||
case iana.ProtocolIPv6ICMP:
|
||||
binary.BigEndian.PutUint16(b[:2], uint16(afiIPv6))
|
||||
binary.BigEndian.PutUint16(b[:2], uint16(iana.AddrFamilyIPv6))
|
||||
copy(b[4:4+net.IPv6len], ifi.Addr.IP.To16())
|
||||
b = b[4+net.IPv6len:]
|
||||
}
|
||||
@@ -145,14 +142,14 @@ func (ifi *InterfaceInfo) parseIPAddr(b []byte) ([]byte, error) {
|
||||
afi := int(binary.BigEndian.Uint16(b[:2]))
|
||||
b = b[4:]
|
||||
switch afi {
|
||||
case afiIPv4:
|
||||
case iana.AddrFamilyIPv4:
|
||||
if len(b) < net.IPv4len {
|
||||
return nil, errMessageTooShort
|
||||
}
|
||||
ifi.Addr.IP = make(net.IP, net.IPv4len)
|
||||
copy(ifi.Addr.IP, b[:net.IPv4len])
|
||||
b = b[net.IPv4len:]
|
||||
case afiIPv6:
|
||||
case iana.AddrFamilyIPv6:
|
||||
if len(b) < net.IPv6len {
|
||||
return nil, errMessageTooShort
|
||||
}
|
||||
@@ -234,3 +231,92 @@ func parseInterfaceInfo(b []byte) (Extension, error) {
|
||||
}
|
||||
return ifi, nil
|
||||
}
|
||||
|
||||
const (
|
||||
classInterfaceIdent = 3
|
||||
typeInterfaceByName = 1
|
||||
typeInterfaceByIndex = 2
|
||||
typeInterfaceByAddress = 3
|
||||
)
|
||||
|
||||
// An InterfaceIdent represents interface identification.
|
||||
type InterfaceIdent struct {
|
||||
Class int // extension object class number
|
||||
Type int // extension object sub-type
|
||||
Name string // interface name
|
||||
Index int // interface index
|
||||
AFI int // address family identifier; see address family numbers in IANA registry
|
||||
Addr []byte // address
|
||||
}
|
||||
|
||||
// Len implements the Len method of Extension interface.
|
||||
func (ifi *InterfaceIdent) Len(_ int) int {
|
||||
switch ifi.Type {
|
||||
case typeInterfaceByName:
|
||||
l := len(ifi.Name)
|
||||
if l > 255 {
|
||||
l = 255
|
||||
}
|
||||
return 4 + (l+3)&^3
|
||||
case typeInterfaceByIndex:
|
||||
return 4 + 8
|
||||
case typeInterfaceByAddress:
|
||||
return 4 + 4 + (len(ifi.Addr)+3)&^3
|
||||
default:
|
||||
return 4
|
||||
}
|
||||
}
|
||||
|
||||
// Marshal implements the Marshal method of Extension interface.
|
||||
func (ifi *InterfaceIdent) Marshal(proto int) ([]byte, error) {
|
||||
b := make([]byte, ifi.Len(proto))
|
||||
if err := ifi.marshal(proto, b); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return b, nil
|
||||
}
|
||||
|
||||
func (ifi *InterfaceIdent) marshal(proto int, b []byte) error {
|
||||
l := ifi.Len(proto)
|
||||
binary.BigEndian.PutUint16(b[:2], uint16(l))
|
||||
b[2], b[3] = classInterfaceIdent, byte(ifi.Type)
|
||||
switch ifi.Type {
|
||||
case typeInterfaceByName:
|
||||
copy(b[4:], ifi.Name)
|
||||
case typeInterfaceByIndex:
|
||||
binary.BigEndian.PutUint64(b[4:4+8], uint64(ifi.Index))
|
||||
case typeInterfaceByAddress:
|
||||
binary.BigEndian.PutUint16(b[4:4+2], uint16(ifi.AFI))
|
||||
b[4+2] = byte(len(ifi.Addr))
|
||||
copy(b[4+4:], ifi.Addr)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func parseInterfaceIdent(b []byte) (Extension, error) {
|
||||
ifi := &InterfaceIdent{
|
||||
Class: int(b[2]),
|
||||
Type: int(b[3]),
|
||||
}
|
||||
switch ifi.Type {
|
||||
case typeInterfaceByName:
|
||||
ifi.Name = strings.Trim(string(b[4:]), string(0))
|
||||
case typeInterfaceByIndex:
|
||||
if len(b[4:]) < 8 {
|
||||
return nil, errInvalidExtension
|
||||
}
|
||||
ifi.Index = int(binary.BigEndian.Uint64(b[4 : 4+8]))
|
||||
case typeInterfaceByAddress:
|
||||
if len(b[4:]) < 4 {
|
||||
return nil, errInvalidExtension
|
||||
}
|
||||
ifi.AFI = int(binary.BigEndian.Uint16(b[4 : 4+2]))
|
||||
l := int(b[4+2])
|
||||
if len(b[4+4:]) < l {
|
||||
return nil, errInvalidExtension
|
||||
}
|
||||
ifi.Addr = make([]byte, l)
|
||||
copy(ifi.Addr, b[4+4:])
|
||||
}
|
||||
return ifi, nil
|
||||
}
|
||||
|
||||
+11
-6
@@ -11,6 +11,7 @@
|
||||
// ICMP extensions for MPLS are defined in RFC 4950.
|
||||
// ICMP extensions for interface and next-hop identification are
|
||||
// defined in RFC 5837.
|
||||
// PROBE: A utility for probing interfaces is defined in RFC 8335.
|
||||
package icmp // import "golang.org/x/net/icmp"
|
||||
|
||||
import (
|
||||
@@ -107,21 +108,25 @@ func (m *Message) Marshal(psh []byte) ([]byte, error) {
|
||||
return b[len(psh):], nil
|
||||
}
|
||||
|
||||
var parseFns = map[Type]func(int, []byte) (MessageBody, error){
|
||||
var parseFns = map[Type]func(int, Type, []byte) (MessageBody, error){
|
||||
ipv4.ICMPTypeDestinationUnreachable: parseDstUnreach,
|
||||
ipv4.ICMPTypeTimeExceeded: parseTimeExceeded,
|
||||
ipv4.ICMPTypeParameterProblem: parseParamProb,
|
||||
|
||||
ipv4.ICMPTypeEcho: parseEcho,
|
||||
ipv4.ICMPTypeEchoReply: parseEcho,
|
||||
ipv4.ICMPTypeEcho: parseEcho,
|
||||
ipv4.ICMPTypeEchoReply: parseEcho,
|
||||
ipv4.ICMPTypeExtendedEchoRequest: parseExtendedEchoRequest,
|
||||
ipv4.ICMPTypeExtendedEchoReply: parseExtendedEchoReply,
|
||||
|
||||
ipv6.ICMPTypeDestinationUnreachable: parseDstUnreach,
|
||||
ipv6.ICMPTypePacketTooBig: parsePacketTooBig,
|
||||
ipv6.ICMPTypeTimeExceeded: parseTimeExceeded,
|
||||
ipv6.ICMPTypeParameterProblem: parseParamProb,
|
||||
|
||||
ipv6.ICMPTypeEchoRequest: parseEcho,
|
||||
ipv6.ICMPTypeEchoReply: parseEcho,
|
||||
ipv6.ICMPTypeEchoRequest: parseEcho,
|
||||
ipv6.ICMPTypeEchoReply: parseEcho,
|
||||
ipv6.ICMPTypeExtendedEchoRequest: parseExtendedEchoRequest,
|
||||
ipv6.ICMPTypeExtendedEchoReply: parseExtendedEchoReply,
|
||||
}
|
||||
|
||||
// ParseMessage parses b as an ICMP message.
|
||||
@@ -143,7 +148,7 @@ func ParseMessage(proto int, b []byte) (*Message, error) {
|
||||
if fn, ok := parseFns[m.Type]; !ok {
|
||||
m.Body, err = parseDefaultMessageBody(proto, b[4:])
|
||||
} else {
|
||||
m.Body, err = fn(proto, b[4:])
|
||||
m.Body, err = fn(proto, m.Type, b[4:])
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
||||
+24
@@ -76,6 +76,18 @@ func TestMarshalAndParseMessage(t *testing.T) {
|
||||
Data: []byte("HELLO-R-U-THERE"),
|
||||
},
|
||||
},
|
||||
{
|
||||
Type: ipv4.ICMPTypeExtendedEchoRequest, Code: 0,
|
||||
Body: &icmp.ExtendedEchoRequest{
|
||||
ID: 1, Seq: 2,
|
||||
},
|
||||
},
|
||||
{
|
||||
Type: ipv4.ICMPTypeExtendedEchoReply, Code: 0,
|
||||
Body: &icmp.ExtendedEchoReply{
|
||||
State: 4 /* Delay */, Active: true, IPv4: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
Type: ipv4.ICMPTypePhoturis,
|
||||
Body: &icmp.DefaultMessageBody{
|
||||
@@ -120,6 +132,18 @@ func TestMarshalAndParseMessage(t *testing.T) {
|
||||
Data: []byte("HELLO-R-U-THERE"),
|
||||
},
|
||||
},
|
||||
{
|
||||
Type: ipv6.ICMPTypeExtendedEchoRequest, Code: 0,
|
||||
Body: &icmp.ExtendedEchoRequest{
|
||||
ID: 1, Seq: 2,
|
||||
},
|
||||
},
|
||||
{
|
||||
Type: ipv6.ICMPTypeExtendedEchoReply, Code: 0,
|
||||
Body: &icmp.ExtendedEchoReply{
|
||||
State: 5 /* Probe */, Active: true, IPv6: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
Type: ipv6.ICMPTypeDuplicateAddressConfirmation,
|
||||
Body: &icmp.DefaultMessageBody{
|
||||
|
||||
+25
-13
@@ -10,12 +10,14 @@ import "golang.org/x/net/internal/iana"
|
||||
// exts as extensions, and returns a required length for message body
|
||||
// and a required length for a padded original datagram in wire
|
||||
// format.
|
||||
func multipartMessageBodyDataLen(proto int, b []byte, exts []Extension) (bodyLen, dataLen int) {
|
||||
func multipartMessageBodyDataLen(proto int, withOrigDgram bool, b []byte, exts []Extension) (bodyLen, dataLen int) {
|
||||
for _, ext := range exts {
|
||||
bodyLen += ext.Len(proto)
|
||||
}
|
||||
if bodyLen > 0 {
|
||||
dataLen = multipartMessageOrigDatagramLen(proto, b)
|
||||
if withOrigDgram {
|
||||
dataLen = multipartMessageOrigDatagramLen(proto, b)
|
||||
}
|
||||
bodyLen += 4 // length of extension header
|
||||
} else {
|
||||
dataLen = len(b)
|
||||
@@ -50,8 +52,8 @@ func multipartMessageOrigDatagramLen(proto int, b []byte) int {
|
||||
// marshalMultipartMessageBody takes data as an original datagram and
|
||||
// exts as extesnsions, and returns a binary encoding of message body.
|
||||
// It can be used for non-multipart message bodies when exts is nil.
|
||||
func marshalMultipartMessageBody(proto int, data []byte, exts []Extension) ([]byte, error) {
|
||||
bodyLen, dataLen := multipartMessageBodyDataLen(proto, data, exts)
|
||||
func marshalMultipartMessageBody(proto int, withOrigDgram bool, data []byte, exts []Extension) ([]byte, error) {
|
||||
bodyLen, dataLen := multipartMessageBodyDataLen(proto, withOrigDgram, data, exts)
|
||||
b := make([]byte, 4+bodyLen)
|
||||
copy(b[4:], data)
|
||||
off := dataLen + 4
|
||||
@@ -71,16 +73,23 @@ func marshalMultipartMessageBody(proto int, data []byte, exts []Extension) ([]by
|
||||
return nil, err
|
||||
}
|
||||
off += ext.Len(proto)
|
||||
case *InterfaceIdent:
|
||||
if err := ext.marshal(proto, b[off:]); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
off += ext.Len(proto)
|
||||
}
|
||||
}
|
||||
s := checksum(b[dataLen+4:])
|
||||
b[dataLen+4+2] ^= byte(s)
|
||||
b[dataLen+4+3] ^= byte(s >> 8)
|
||||
switch proto {
|
||||
case iana.ProtocolICMP:
|
||||
b[1] = byte(dataLen / 4)
|
||||
case iana.ProtocolIPv6ICMP:
|
||||
b[0] = byte(dataLen / 8)
|
||||
if withOrigDgram {
|
||||
switch proto {
|
||||
case iana.ProtocolICMP:
|
||||
b[1] = byte(dataLen / 4)
|
||||
case iana.ProtocolIPv6ICMP:
|
||||
b[0] = byte(dataLen / 8)
|
||||
}
|
||||
}
|
||||
}
|
||||
return b, nil
|
||||
@@ -88,7 +97,7 @@ func marshalMultipartMessageBody(proto int, data []byte, exts []Extension) ([]by
|
||||
|
||||
// parseMultipartMessageBody parses b as either a non-multipart
|
||||
// message body or a multipart message body.
|
||||
func parseMultipartMessageBody(proto int, b []byte) ([]byte, []Extension, error) {
|
||||
func parseMultipartMessageBody(proto int, typ Type, b []byte) ([]byte, []Extension, error) {
|
||||
var l int
|
||||
switch proto {
|
||||
case iana.ProtocolICMP:
|
||||
@@ -99,11 +108,14 @@ func parseMultipartMessageBody(proto int, b []byte) ([]byte, []Extension, error)
|
||||
if len(b) == 4 {
|
||||
return nil, nil, nil
|
||||
}
|
||||
exts, l, err := parseExtensions(b[4:], l)
|
||||
exts, l, err := parseExtensions(typ, b[4:], l)
|
||||
if err != nil {
|
||||
l = len(b) - 4
|
||||
}
|
||||
data := make([]byte, l)
|
||||
copy(data, b[4:])
|
||||
var data []byte
|
||||
if l > 0 {
|
||||
data = make([]byte, l)
|
||||
copy(data, b[4:])
|
||||
}
|
||||
return data, exts, nil
|
||||
}
|
||||
|
||||
+142
-10
@@ -23,17 +23,21 @@ func TestMarshalAndParseMultipartMessage(t *testing.T) {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
switch proto {
|
||||
case iana.ProtocolICMP:
|
||||
if b[5] != 32 {
|
||||
return fmt.Errorf("got %d; want 32", b[5])
|
||||
}
|
||||
case iana.ProtocolIPv6ICMP:
|
||||
if b[4] != 16 {
|
||||
return fmt.Errorf("got %d; want 16", b[4])
|
||||
}
|
||||
switch tm.Type {
|
||||
case ipv4.ICMPTypeExtendedEchoRequest, ipv6.ICMPTypeExtendedEchoRequest:
|
||||
default:
|
||||
return fmt.Errorf("unknown protocol: %d", proto)
|
||||
switch proto {
|
||||
case iana.ProtocolICMP:
|
||||
if b[5] != 32 {
|
||||
return fmt.Errorf("got %d; want 32", b[5])
|
||||
}
|
||||
case iana.ProtocolIPv6ICMP:
|
||||
if b[4] != 16 {
|
||||
return fmt.Errorf("got %d; want 16", b[4])
|
||||
}
|
||||
default:
|
||||
return fmt.Errorf("unknown protocol: %d", proto)
|
||||
}
|
||||
}
|
||||
m, err := icmp.ParseMessage(proto, b)
|
||||
if err != nil {
|
||||
@@ -43,6 +47,11 @@ func TestMarshalAndParseMultipartMessage(t *testing.T) {
|
||||
return fmt.Errorf("got %v; want %v", m, &tm)
|
||||
}
|
||||
switch m.Type {
|
||||
case ipv4.ICMPTypeExtendedEchoRequest, ipv6.ICMPTypeExtendedEchoRequest:
|
||||
got, want := m.Body.(*icmp.ExtendedEchoRequest), tm.Body.(*icmp.ExtendedEchoRequest)
|
||||
if !reflect.DeepEqual(got.Extensions, want.Extensions) {
|
||||
return errors.New(dumpExtensions(got.Extensions, want.Extensions))
|
||||
}
|
||||
case ipv4.ICMPTypeDestinationUnreachable:
|
||||
got, want := m.Body.(*icmp.DstUnreach), tm.Body.(*icmp.DstUnreach)
|
||||
if !reflect.DeepEqual(got.Extensions, want.Extensions) {
|
||||
@@ -200,6 +209,51 @@ func TestMarshalAndParseMultipartMessage(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Type: ipv4.ICMPTypeExtendedEchoRequest, Code: 0,
|
||||
Body: &icmp.ExtendedEchoRequest{
|
||||
ID: 1, Seq: 2, Local: true,
|
||||
Extensions: []icmp.Extension{
|
||||
&icmp.InterfaceIdent{
|
||||
Class: 3,
|
||||
Type: 1,
|
||||
Name: "en101",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Type: ipv4.ICMPTypeExtendedEchoRequest, Code: 0,
|
||||
Body: &icmp.ExtendedEchoRequest{
|
||||
ID: 1, Seq: 2, Local: true,
|
||||
Extensions: []icmp.Extension{
|
||||
&icmp.InterfaceIdent{
|
||||
Class: 3,
|
||||
Type: 2,
|
||||
Index: 911,
|
||||
},
|
||||
&icmp.InterfaceIdent{
|
||||
Class: 3,
|
||||
Type: 1,
|
||||
Name: "en101",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Type: ipv4.ICMPTypeExtendedEchoRequest, Code: 0,
|
||||
Body: &icmp.ExtendedEchoRequest{
|
||||
ID: 1, Seq: 2,
|
||||
Extensions: []icmp.Extension{
|
||||
&icmp.InterfaceIdent{
|
||||
Class: 3,
|
||||
Type: 3,
|
||||
AFI: iana.AddrFamily48bitMAC,
|
||||
Addr: []byte{0x01, 0x23, 0x45, 0x67, 0x89, 0xab},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
} {
|
||||
if err := fn(t, iana.ProtocolICMP, tm); err != nil {
|
||||
t.Errorf("#%d: %v", i, err)
|
||||
@@ -287,6 +341,51 @@ func TestMarshalAndParseMultipartMessage(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Type: ipv6.ICMPTypeExtendedEchoRequest, Code: 0,
|
||||
Body: &icmp.ExtendedEchoRequest{
|
||||
ID: 1, Seq: 2, Local: true,
|
||||
Extensions: []icmp.Extension{
|
||||
&icmp.InterfaceIdent{
|
||||
Class: 3,
|
||||
Type: 1,
|
||||
Name: "en101",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Type: ipv6.ICMPTypeExtendedEchoRequest, Code: 0,
|
||||
Body: &icmp.ExtendedEchoRequest{
|
||||
ID: 1, Seq: 2, Local: true,
|
||||
Extensions: []icmp.Extension{
|
||||
&icmp.InterfaceIdent{
|
||||
Class: 3,
|
||||
Type: 1,
|
||||
Name: "en101",
|
||||
},
|
||||
&icmp.InterfaceIdent{
|
||||
Class: 3,
|
||||
Type: 2,
|
||||
Index: 911,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Type: ipv6.ICMPTypeExtendedEchoRequest, Code: 0,
|
||||
Body: &icmp.ExtendedEchoRequest{
|
||||
ID: 1, Seq: 2,
|
||||
Extensions: []icmp.Extension{
|
||||
&icmp.InterfaceIdent{
|
||||
Class: 3,
|
||||
Type: 3,
|
||||
AFI: iana.AddrFamilyIPv4,
|
||||
Addr: []byte{192, 0, 2, 1},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
} {
|
||||
if err := fn(t, iana.ProtocolIPv6ICMP, tm); err != nil {
|
||||
t.Errorf("#%d: %v", i, err)
|
||||
@@ -309,6 +408,11 @@ func dumpExtensions(gotExts, wantExts []icmp.Extension) string {
|
||||
if !reflect.DeepEqual(got, want) {
|
||||
s += fmt.Sprintf("#%d: got %#v, %#v, %#v; want %#v, %#v, %#v\n", i, got, got.Interface, got.Addr, want, want.Interface, want.Addr)
|
||||
}
|
||||
case *icmp.InterfaceIdent:
|
||||
want := wantExts[i].(*icmp.InterfaceIdent)
|
||||
if !reflect.DeepEqual(got, want) {
|
||||
s += fmt.Sprintf("#%d: got %#v; want %#v\n", i, got, want)
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(s) == 0 {
|
||||
@@ -435,6 +539,34 @@ func TestMultipartMessageBodyLen(t *testing.T) {
|
||||
},
|
||||
4 + 4 + 4 + 0 + 136, // [length, unused], extension header, object header, object payload and original datagram
|
||||
},
|
||||
|
||||
{
|
||||
iana.ProtocolICMP,
|
||||
&icmp.ExtendedEchoRequest{},
|
||||
4, // [id, seq, l-bit]
|
||||
},
|
||||
{
|
||||
iana.ProtocolICMP,
|
||||
&icmp.ExtendedEchoRequest{
|
||||
Extensions: []icmp.Extension{
|
||||
&icmp.InterfaceIdent{},
|
||||
},
|
||||
},
|
||||
4 + 4 + 4, // [id, seq, l-bit], extension header, object header
|
||||
},
|
||||
{
|
||||
iana.ProtocolIPv6ICMP,
|
||||
&icmp.ExtendedEchoRequest{
|
||||
Extensions: []icmp.Extension{
|
||||
&icmp.InterfaceIdent{
|
||||
Type: 3,
|
||||
AFI: iana.AddrFamilyNSAP,
|
||||
Addr: []byte{0x49, 0x00, 0x01, 0xaa, 0xaa, 0xbb, 0xbb, 0xcc, 0xcc, 0x00},
|
||||
},
|
||||
},
|
||||
},
|
||||
4 + 4 + 4 + 16, // [id, seq, l-bit], extension header, object header, object payload
|
||||
},
|
||||
} {
|
||||
if out := tt.in.Len(tt.proto); out != tt.out {
|
||||
t.Errorf("#%d: got %d; want %d", i, out, tt.out)
|
||||
|
||||
+1
-1
@@ -29,7 +29,7 @@ func (p *PacketTooBig) Marshal(proto int) ([]byte, error) {
|
||||
}
|
||||
|
||||
// parsePacketTooBig parses b as an ICMP packet too big message body.
|
||||
func parsePacketTooBig(proto int, b []byte) (MessageBody, error) {
|
||||
func parsePacketTooBig(proto int, _ Type, b []byte) (MessageBody, error) {
|
||||
bodyLen := len(b)
|
||||
if bodyLen < 4 {
|
||||
return nil, errMessageTooShort
|
||||
|
||||
+4
-4
@@ -21,7 +21,7 @@ func (p *ParamProb) Len(proto int) int {
|
||||
if p == nil {
|
||||
return 0
|
||||
}
|
||||
l, _ := multipartMessageBodyDataLen(proto, p.Data, p.Extensions)
|
||||
l, _ := multipartMessageBodyDataLen(proto, true, p.Data, p.Extensions)
|
||||
return 4 + l
|
||||
}
|
||||
|
||||
@@ -33,7 +33,7 @@ func (p *ParamProb) Marshal(proto int) ([]byte, error) {
|
||||
copy(b[4:], p.Data)
|
||||
return b, nil
|
||||
}
|
||||
b, err := marshalMultipartMessageBody(proto, p.Data, p.Extensions)
|
||||
b, err := marshalMultipartMessageBody(proto, true, p.Data, p.Extensions)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -42,7 +42,7 @@ func (p *ParamProb) Marshal(proto int) ([]byte, error) {
|
||||
}
|
||||
|
||||
// parseParamProb parses b as an ICMP parameter problem message body.
|
||||
func parseParamProb(proto int, b []byte) (MessageBody, error) {
|
||||
func parseParamProb(proto int, typ Type, b []byte) (MessageBody, error) {
|
||||
if len(b) < 4 {
|
||||
return nil, errMessageTooShort
|
||||
}
|
||||
@@ -55,7 +55,7 @@ func parseParamProb(proto int, b []byte) (MessageBody, error) {
|
||||
}
|
||||
p.Pointer = uintptr(b[0])
|
||||
var err error
|
||||
p.Data, p.Extensions, err = parseMultipartMessageBody(proto, b)
|
||||
p.Data, p.Extensions, err = parseMultipartMessageBody(proto, typ, b)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
+4
-4
@@ -15,23 +15,23 @@ func (p *TimeExceeded) Len(proto int) int {
|
||||
if p == nil {
|
||||
return 0
|
||||
}
|
||||
l, _ := multipartMessageBodyDataLen(proto, p.Data, p.Extensions)
|
||||
l, _ := multipartMessageBodyDataLen(proto, true, p.Data, p.Extensions)
|
||||
return 4 + l
|
||||
}
|
||||
|
||||
// Marshal implements the Marshal method of MessageBody interface.
|
||||
func (p *TimeExceeded) Marshal(proto int) ([]byte, error) {
|
||||
return marshalMultipartMessageBody(proto, p.Data, p.Extensions)
|
||||
return marshalMultipartMessageBody(proto, true, p.Data, p.Extensions)
|
||||
}
|
||||
|
||||
// parseTimeExceeded parses b as an ICMP time exceeded message body.
|
||||
func parseTimeExceeded(proto int, b []byte) (MessageBody, error) {
|
||||
func parseTimeExceeded(proto int, typ Type, b []byte) (MessageBody, error) {
|
||||
if len(b) < 4 {
|
||||
return nil, errMessageTooShort
|
||||
}
|
||||
p := &TimeExceeded{}
|
||||
var err error
|
||||
p.Data, p.Extensions, err = parseMultipartMessageBody(proto, b)
|
||||
p.Data, p.Extensions, err = parseMultipartMessageBody(proto, typ, b)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
+1
-1
@@ -2,7 +2,7 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build nacl plan9
|
||||
// +build js,wasm nacl plan9
|
||||
|
||||
package nettest
|
||||
|
||||
|
||||
+2
-2
@@ -64,7 +64,7 @@ func TestableNetwork(network string) bool {
|
||||
switch network {
|
||||
case "unix", "unixgram":
|
||||
switch runtime.GOOS {
|
||||
case "android", "nacl", "plan9", "windows":
|
||||
case "android", "js", "nacl", "plan9", "windows":
|
||||
return false
|
||||
}
|
||||
if runtime.GOOS == "darwin" && (runtime.GOARCH == "arm" || runtime.GOARCH == "arm64") {
|
||||
@@ -72,7 +72,7 @@ func TestableNetwork(network string) bool {
|
||||
}
|
||||
case "unixpacket":
|
||||
switch runtime.GOOS {
|
||||
case "android", "darwin", "freebsd", "nacl", "plan9", "windows":
|
||||
case "android", "darwin", "freebsd", "js", "nacl", "plan9", "windows":
|
||||
return false
|
||||
case "netbsd":
|
||||
// It passes on amd64 at least. 386 fails (Issue 22927). arm is unknown.
|
||||
|
||||
+168
@@ -0,0 +1,168 @@
|
||||
// Copyright 2018 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package socks
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"io"
|
||||
"net"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
var (
|
||||
noDeadline = time.Time{}
|
||||
aLongTimeAgo = time.Unix(1, 0)
|
||||
)
|
||||
|
||||
func (d *Dialer) connect(ctx context.Context, c net.Conn, address string) (_ net.Addr, ctxErr error) {
|
||||
host, port, err := splitHostPort(address)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if deadline, ok := ctx.Deadline(); ok && !deadline.IsZero() {
|
||||
c.SetDeadline(deadline)
|
||||
defer c.SetDeadline(noDeadline)
|
||||
}
|
||||
if ctx != context.Background() {
|
||||
errCh := make(chan error, 1)
|
||||
done := make(chan struct{})
|
||||
defer func() {
|
||||
close(done)
|
||||
if ctxErr == nil {
|
||||
ctxErr = <-errCh
|
||||
}
|
||||
}()
|
||||
go func() {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
c.SetDeadline(aLongTimeAgo)
|
||||
errCh <- ctx.Err()
|
||||
case <-done:
|
||||
errCh <- nil
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
b := make([]byte, 0, 6+len(host)) // the size here is just an estimate
|
||||
b = append(b, Version5)
|
||||
if len(d.AuthMethods) == 0 || d.Authenticate == nil {
|
||||
b = append(b, 1, byte(AuthMethodNotRequired))
|
||||
} else {
|
||||
ams := d.AuthMethods
|
||||
if len(ams) > 255 {
|
||||
return nil, errors.New("too many authentication methods")
|
||||
}
|
||||
b = append(b, byte(len(ams)))
|
||||
for _, am := range ams {
|
||||
b = append(b, byte(am))
|
||||
}
|
||||
}
|
||||
if _, ctxErr = c.Write(b); ctxErr != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if _, ctxErr = io.ReadFull(c, b[:2]); ctxErr != nil {
|
||||
return
|
||||
}
|
||||
if b[0] != Version5 {
|
||||
return nil, errors.New("unexpected protocol version " + strconv.Itoa(int(b[0])))
|
||||
}
|
||||
am := AuthMethod(b[1])
|
||||
if am == AuthMethodNoAcceptableMethods {
|
||||
return nil, errors.New("no acceptable authentication methods")
|
||||
}
|
||||
if d.Authenticate != nil {
|
||||
if ctxErr = d.Authenticate(ctx, c, am); ctxErr != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
b = b[:0]
|
||||
b = append(b, Version5, byte(d.cmd), 0)
|
||||
if ip := net.ParseIP(host); ip != nil {
|
||||
if ip4 := ip.To4(); ip4 != nil {
|
||||
b = append(b, AddrTypeIPv4)
|
||||
b = append(b, ip4...)
|
||||
} else if ip6 := ip.To16(); ip6 != nil {
|
||||
b = append(b, AddrTypeIPv6)
|
||||
b = append(b, ip6...)
|
||||
} else {
|
||||
return nil, errors.New("unknown address type")
|
||||
}
|
||||
} else {
|
||||
if len(host) > 255 {
|
||||
return nil, errors.New("FQDN too long")
|
||||
}
|
||||
b = append(b, AddrTypeFQDN)
|
||||
b = append(b, byte(len(host)))
|
||||
b = append(b, host...)
|
||||
}
|
||||
b = append(b, byte(port>>8), byte(port))
|
||||
if _, ctxErr = c.Write(b); ctxErr != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if _, ctxErr = io.ReadFull(c, b[:4]); ctxErr != nil {
|
||||
return
|
||||
}
|
||||
if b[0] != Version5 {
|
||||
return nil, errors.New("unexpected protocol version " + strconv.Itoa(int(b[0])))
|
||||
}
|
||||
if cmdErr := Reply(b[1]); cmdErr != StatusSucceeded {
|
||||
return nil, errors.New("unknown error " + cmdErr.String())
|
||||
}
|
||||
if b[2] != 0 {
|
||||
return nil, errors.New("non-zero reserved field")
|
||||
}
|
||||
l := 2
|
||||
var a Addr
|
||||
switch b[3] {
|
||||
case AddrTypeIPv4:
|
||||
l += net.IPv4len
|
||||
a.IP = make(net.IP, net.IPv4len)
|
||||
case AddrTypeIPv6:
|
||||
l += net.IPv6len
|
||||
a.IP = make(net.IP, net.IPv6len)
|
||||
case AddrTypeFQDN:
|
||||
if _, err := io.ReadFull(c, b[:1]); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
l += int(b[0])
|
||||
default:
|
||||
return nil, errors.New("unknown address type " + strconv.Itoa(int(b[3])))
|
||||
}
|
||||
if cap(b) < l {
|
||||
b = make([]byte, l)
|
||||
} else {
|
||||
b = b[:l]
|
||||
}
|
||||
if _, ctxErr = io.ReadFull(c, b); ctxErr != nil {
|
||||
return
|
||||
}
|
||||
if a.IP != nil {
|
||||
copy(a.IP, b)
|
||||
} else {
|
||||
a.Name = string(b[:len(b)-2])
|
||||
}
|
||||
a.Port = int(b[len(b)-2])<<8 | int(b[len(b)-1])
|
||||
return &a, nil
|
||||
}
|
||||
|
||||
func splitHostPort(address string) (string, int, error) {
|
||||
host, port, err := net.SplitHostPort(address)
|
||||
if err != nil {
|
||||
return "", 0, err
|
||||
}
|
||||
portnum, err := strconv.Atoi(port)
|
||||
if err != nil {
|
||||
return "", 0, err
|
||||
}
|
||||
if 1 > portnum || portnum > 0xffff {
|
||||
return "", 0, errors.New("port number out of range " + port)
|
||||
}
|
||||
return host, portnum, nil
|
||||
}
|
||||
+158
@@ -0,0 +1,158 @@
|
||||
// Copyright 2018 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package socks_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
"math/rand"
|
||||
"net"
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"golang.org/x/net/internal/socks"
|
||||
"golang.org/x/net/internal/sockstest"
|
||||
)
|
||||
|
||||
const (
|
||||
targetNetwork = "tcp6"
|
||||
targetHostname = "fqdn.doesnotexist"
|
||||
targetHostIP = "2001:db8::1"
|
||||
targetPort = "5963"
|
||||
)
|
||||
|
||||
func TestDial(t *testing.T) {
|
||||
t.Run("Connect", func(t *testing.T) {
|
||||
ss, err := sockstest.NewServer(sockstest.NoAuthRequired, sockstest.NoProxyRequired)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
defer ss.Close()
|
||||
d := socks.NewDialer(ss.Addr().Network(), ss.Addr().String())
|
||||
d.AuthMethods = []socks.AuthMethod{
|
||||
socks.AuthMethodNotRequired,
|
||||
socks.AuthMethodUsernamePassword,
|
||||
}
|
||||
d.Authenticate = (&socks.UsernamePassword{
|
||||
Username: "username",
|
||||
Password: "password",
|
||||
}).Authenticate
|
||||
c, err := d.Dial(targetNetwork, net.JoinHostPort(targetHostIP, targetPort))
|
||||
if err == nil {
|
||||
c.(*socks.Conn).BoundAddr()
|
||||
c.Close()
|
||||
}
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
})
|
||||
t.Run("Cancel", func(t *testing.T) {
|
||||
ss, err := sockstest.NewServer(sockstest.NoAuthRequired, blackholeCmdFunc)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
defer ss.Close()
|
||||
d := socks.NewDialer(ss.Addr().Network(), ss.Addr().String())
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
dialErr := make(chan error)
|
||||
go func() {
|
||||
c, err := d.DialContext(ctx, ss.TargetAddr().Network(), net.JoinHostPort(targetHostname, targetPort))
|
||||
if err == nil {
|
||||
c.Close()
|
||||
}
|
||||
dialErr <- err
|
||||
}()
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
cancel()
|
||||
err = <-dialErr
|
||||
if perr, nerr := parseDialError(err); perr != context.Canceled && nerr == nil {
|
||||
t.Errorf("got %v; want context.Canceled or equivalent", err)
|
||||
return
|
||||
}
|
||||
})
|
||||
t.Run("Deadline", func(t *testing.T) {
|
||||
ss, err := sockstest.NewServer(sockstest.NoAuthRequired, blackholeCmdFunc)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
defer ss.Close()
|
||||
d := socks.NewDialer(ss.Addr().Network(), ss.Addr().String())
|
||||
ctx, cancel := context.WithDeadline(context.Background(), time.Now().Add(100*time.Millisecond))
|
||||
defer cancel()
|
||||
c, err := d.DialContext(ctx, ss.TargetAddr().Network(), net.JoinHostPort(targetHostname, targetPort))
|
||||
if err == nil {
|
||||
c.Close()
|
||||
}
|
||||
if perr, nerr := parseDialError(err); perr != context.DeadlineExceeded && nerr == nil {
|
||||
t.Errorf("got %v; want context.DeadlineExceeded or equivalent", err)
|
||||
return
|
||||
}
|
||||
})
|
||||
t.Run("WithRogueServer", func(t *testing.T) {
|
||||
ss, err := sockstest.NewServer(sockstest.NoAuthRequired, rogueCmdFunc)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
defer ss.Close()
|
||||
d := socks.NewDialer(ss.Addr().Network(), ss.Addr().String())
|
||||
for i := 0; i < 2*len(rogueCmdList); i++ {
|
||||
ctx, cancel := context.WithDeadline(context.Background(), time.Now().Add(100*time.Millisecond))
|
||||
defer cancel()
|
||||
c, err := d.DialContext(ctx, targetNetwork, net.JoinHostPort(targetHostIP, targetPort))
|
||||
if err == nil {
|
||||
t.Log(c.(*socks.Conn).BoundAddr())
|
||||
c.Close()
|
||||
t.Error("should fail")
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func blackholeCmdFunc(rw io.ReadWriter, b []byte) error {
|
||||
if _, err := sockstest.ParseCmdRequest(b); err != nil {
|
||||
return err
|
||||
}
|
||||
var bb [1]byte
|
||||
for {
|
||||
if _, err := rw.Read(bb[:]); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func rogueCmdFunc(rw io.ReadWriter, b []byte) error {
|
||||
if _, err := sockstest.ParseCmdRequest(b); err != nil {
|
||||
return err
|
||||
}
|
||||
rw.Write(rogueCmdList[rand.Intn(len(rogueCmdList))])
|
||||
return nil
|
||||
}
|
||||
|
||||
var rogueCmdList = [][]byte{
|
||||
{0x05},
|
||||
{0x06, 0x00, 0x00, 0x01, 192, 0, 2, 1, 0x17, 0x4b},
|
||||
{0x05, 0x00, 0xff, 0x01, 192, 0, 2, 2, 0x17, 0x4b},
|
||||
{0x05, 0x00, 0x00, 0x01, 192, 0, 2, 3},
|
||||
{0x05, 0x00, 0x00, 0x03, 0x04, 'F', 'Q', 'D', 'N'},
|
||||
}
|
||||
|
||||
func parseDialError(err error) (perr, nerr error) {
|
||||
if e, ok := err.(*net.OpError); ok {
|
||||
err = e.Err
|
||||
nerr = e
|
||||
}
|
||||
if e, ok := err.(*os.SyscallError); ok {
|
||||
err = e.Err
|
||||
}
|
||||
perr = err
|
||||
return
|
||||
}
|
||||
+265
@@ -0,0 +1,265 @@
|
||||
// Copyright 2018 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Package socks provides a SOCKS version 5 client implementation.
|
||||
//
|
||||
// SOCKS protocol version 5 is defined in RFC 1928.
|
||||
// Username/Password authentication for SOCKS version 5 is defined in
|
||||
// RFC 1929.
|
||||
package socks
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"io"
|
||||
"net"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
// A Command represents a SOCKS command.
|
||||
type Command int
|
||||
|
||||
func (cmd Command) String() string {
|
||||
switch cmd {
|
||||
case CmdConnect:
|
||||
return "socks connect"
|
||||
case cmdBind:
|
||||
return "socks bind"
|
||||
default:
|
||||
return "socks " + strconv.Itoa(int(cmd))
|
||||
}
|
||||
}
|
||||
|
||||
// An AuthMethod represents a SOCKS authentication method.
|
||||
type AuthMethod int
|
||||
|
||||
// A Reply represents a SOCKS command reply code.
|
||||
type Reply int
|
||||
|
||||
func (code Reply) String() string {
|
||||
switch code {
|
||||
case StatusSucceeded:
|
||||
return "succeeded"
|
||||
case 0x01:
|
||||
return "general SOCKS server failure"
|
||||
case 0x02:
|
||||
return "connection not allowed by ruleset"
|
||||
case 0x03:
|
||||
return "network unreachable"
|
||||
case 0x04:
|
||||
return "host unreachable"
|
||||
case 0x05:
|
||||
return "connection refused"
|
||||
case 0x06:
|
||||
return "TTL expired"
|
||||
case 0x07:
|
||||
return "command not supported"
|
||||
case 0x08:
|
||||
return "address type not supported"
|
||||
default:
|
||||
return "unknown code: " + strconv.Itoa(int(code))
|
||||
}
|
||||
}
|
||||
|
||||
// Wire protocol constants.
|
||||
const (
|
||||
Version5 = 0x05
|
||||
|
||||
AddrTypeIPv4 = 0x01
|
||||
AddrTypeFQDN = 0x03
|
||||
AddrTypeIPv6 = 0x04
|
||||
|
||||
CmdConnect Command = 0x01 // establishes an active-open forward proxy connection
|
||||
cmdBind Command = 0x02 // establishes a passive-open forward proxy connection
|
||||
|
||||
AuthMethodNotRequired AuthMethod = 0x00 // no authentication required
|
||||
AuthMethodUsernamePassword AuthMethod = 0x02 // use username/password
|
||||
AuthMethodNoAcceptableMethods AuthMethod = 0xff // no acceptable authetication methods
|
||||
|
||||
StatusSucceeded Reply = 0x00
|
||||
)
|
||||
|
||||
// An Addr represents a SOCKS-specific address.
|
||||
// Either Name or IP is used exclusively.
|
||||
type Addr struct {
|
||||
Name string // fully-qualified domain name
|
||||
IP net.IP
|
||||
Port int
|
||||
}
|
||||
|
||||
func (a *Addr) Network() string { return "socks" }
|
||||
|
||||
func (a *Addr) String() string {
|
||||
if a == nil {
|
||||
return "<nil>"
|
||||
}
|
||||
port := strconv.Itoa(a.Port)
|
||||
if a.IP == nil {
|
||||
return net.JoinHostPort(a.Name, port)
|
||||
}
|
||||
return net.JoinHostPort(a.IP.String(), port)
|
||||
}
|
||||
|
||||
// A Conn represents a forward proxy connection.
|
||||
type Conn struct {
|
||||
net.Conn
|
||||
|
||||
boundAddr net.Addr
|
||||
}
|
||||
|
||||
// BoundAddr returns the address assigned by the proxy server for
|
||||
// connecting to the command target address from the proxy server.
|
||||
func (c *Conn) BoundAddr() net.Addr {
|
||||
if c == nil {
|
||||
return nil
|
||||
}
|
||||
return c.boundAddr
|
||||
}
|
||||
|
||||
// A Dialer holds SOCKS-specific options.
|
||||
type Dialer struct {
|
||||
cmd Command // either CmdConnect or cmdBind
|
||||
proxyNetwork string // network between a proxy server and a client
|
||||
proxyAddress string // proxy server address
|
||||
|
||||
// ProxyDial specifies the optional dial function for
|
||||
// establishing the transport connection.
|
||||
ProxyDial func(context.Context, string, string) (net.Conn, error)
|
||||
|
||||
// AuthMethods specifies the list of request authention
|
||||
// methods.
|
||||
// If empty, SOCKS client requests only AuthMethodNotRequired.
|
||||
AuthMethods []AuthMethod
|
||||
|
||||
// Authenticate specifies the optional authentication
|
||||
// function. It must be non-nil when AuthMethods is not empty.
|
||||
// It must return an error when the authentication is failed.
|
||||
Authenticate func(context.Context, io.ReadWriter, AuthMethod) error
|
||||
}
|
||||
|
||||
// DialContext connects to the provided address on the provided
|
||||
// network.
|
||||
//
|
||||
// The returned error value may be a net.OpError. When the Op field of
|
||||
// net.OpError contains "socks", the Source field contains a proxy
|
||||
// server address and the Addr field contains a command target
|
||||
// address.
|
||||
//
|
||||
// See func Dial of the net package of standard library for a
|
||||
// description of the network and address parameters.
|
||||
func (d *Dialer) DialContext(ctx context.Context, network, address string) (net.Conn, error) {
|
||||
switch network {
|
||||
case "tcp", "tcp6", "tcp4":
|
||||
default:
|
||||
proxy, dst, _ := d.pathAddrs(address)
|
||||
return nil, &net.OpError{Op: d.cmd.String(), Net: network, Source: proxy, Addr: dst, Err: errors.New("network not implemented")}
|
||||
}
|
||||
switch d.cmd {
|
||||
case CmdConnect, cmdBind:
|
||||
default:
|
||||
proxy, dst, _ := d.pathAddrs(address)
|
||||
return nil, &net.OpError{Op: d.cmd.String(), Net: network, Source: proxy, Addr: dst, Err: errors.New("command not implemented")}
|
||||
}
|
||||
if ctx == nil {
|
||||
ctx = context.Background()
|
||||
}
|
||||
var err error
|
||||
var c net.Conn
|
||||
if d.ProxyDial != nil {
|
||||
c, err = d.ProxyDial(ctx, d.proxyNetwork, d.proxyAddress)
|
||||
} else {
|
||||
var dd net.Dialer
|
||||
c, err = dd.DialContext(ctx, d.proxyNetwork, d.proxyAddress)
|
||||
}
|
||||
if err != nil {
|
||||
proxy, dst, _ := d.pathAddrs(address)
|
||||
return nil, &net.OpError{Op: d.cmd.String(), Net: network, Source: proxy, Addr: dst, Err: err}
|
||||
}
|
||||
a, err := d.connect(ctx, c, address)
|
||||
if err != nil {
|
||||
c.Close()
|
||||
proxy, dst, _ := d.pathAddrs(address)
|
||||
return nil, &net.OpError{Op: d.cmd.String(), Net: network, Source: proxy, Addr: dst, Err: err}
|
||||
}
|
||||
return &Conn{Conn: c, boundAddr: a}, nil
|
||||
}
|
||||
|
||||
// Dial connects to the provided address on the provided network.
|
||||
//
|
||||
// Deprecated: Use DialContext instead.
|
||||
func (d *Dialer) Dial(network, address string) (net.Conn, error) {
|
||||
return d.DialContext(context.Background(), network, address)
|
||||
}
|
||||
|
||||
func (d *Dialer) pathAddrs(address string) (proxy, dst net.Addr, err error) {
|
||||
for i, s := range []string{d.proxyAddress, address} {
|
||||
host, port, err := splitHostPort(s)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
a := &Addr{Port: port}
|
||||
a.IP = net.ParseIP(host)
|
||||
if a.IP == nil {
|
||||
a.Name = host
|
||||
}
|
||||
if i == 0 {
|
||||
proxy = a
|
||||
} else {
|
||||
dst = a
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// NewDialer returns a new Dialer that dials through the provided
|
||||
// proxy server's network and address.
|
||||
func NewDialer(network, address string) *Dialer {
|
||||
return &Dialer{proxyNetwork: network, proxyAddress: address, cmd: CmdConnect}
|
||||
}
|
||||
|
||||
const (
|
||||
authUsernamePasswordVersion = 0x01
|
||||
authStatusSucceeded = 0x00
|
||||
)
|
||||
|
||||
// UsernamePassword are the credentials for the username/password
|
||||
// authentication method.
|
||||
type UsernamePassword struct {
|
||||
Username string
|
||||
Password string
|
||||
}
|
||||
|
||||
// Authenticate authenticates a pair of username and password with the
|
||||
// proxy server.
|
||||
func (up *UsernamePassword) Authenticate(ctx context.Context, rw io.ReadWriter, auth AuthMethod) error {
|
||||
switch auth {
|
||||
case AuthMethodNotRequired:
|
||||
return nil
|
||||
case AuthMethodUsernamePassword:
|
||||
if len(up.Username) == 0 || len(up.Username) > 255 || len(up.Password) == 0 || len(up.Password) > 255 {
|
||||
return errors.New("invalid username/password")
|
||||
}
|
||||
b := []byte{authUsernamePasswordVersion}
|
||||
b = append(b, byte(len(up.Username)))
|
||||
b = append(b, up.Username...)
|
||||
b = append(b, byte(len(up.Password)))
|
||||
b = append(b, up.Password...)
|
||||
// TODO(mikio): handle IO deadlines and cancelation if
|
||||
// necessary
|
||||
if _, err := rw.Write(b); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := io.ReadFull(rw, b[:2]); err != nil {
|
||||
return err
|
||||
}
|
||||
if b[0] != authUsernamePasswordVersion {
|
||||
return errors.New("invalid username/password version")
|
||||
}
|
||||
if b[1] != authStatusSucceeded {
|
||||
return errors.New("username/password authentication failed")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
return errors.New("unsupported authentication method " + strconv.Itoa(int(auth)))
|
||||
}
|
||||
+241
@@ -0,0 +1,241 @@
|
||||
// Copyright 2018 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Package sockstest provides utilities for SOCKS testing.
|
||||
package sockstest
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io"
|
||||
"net"
|
||||
|
||||
"golang.org/x/net/internal/nettest"
|
||||
"golang.org/x/net/internal/socks"
|
||||
)
|
||||
|
||||
// An AuthRequest represents an authentication request.
|
||||
type AuthRequest struct {
|
||||
Version int
|
||||
Methods []socks.AuthMethod
|
||||
}
|
||||
|
||||
// ParseAuthRequest parses an authentication request.
|
||||
func ParseAuthRequest(b []byte) (*AuthRequest, error) {
|
||||
if len(b) < 2 {
|
||||
return nil, errors.New("short auth request")
|
||||
}
|
||||
if b[0] != socks.Version5 {
|
||||
return nil, errors.New("unexpected protocol version")
|
||||
}
|
||||
if len(b)-2 < int(b[1]) {
|
||||
return nil, errors.New("short auth request")
|
||||
}
|
||||
req := &AuthRequest{Version: int(b[0])}
|
||||
if b[1] > 0 {
|
||||
req.Methods = make([]socks.AuthMethod, b[1])
|
||||
for i, m := range b[2 : 2+b[1]] {
|
||||
req.Methods[i] = socks.AuthMethod(m)
|
||||
}
|
||||
}
|
||||
return req, nil
|
||||
}
|
||||
|
||||
// MarshalAuthReply returns an authentication reply in wire format.
|
||||
func MarshalAuthReply(ver int, m socks.AuthMethod) ([]byte, error) {
|
||||
return []byte{byte(ver), byte(m)}, nil
|
||||
}
|
||||
|
||||
// A CmdRequest repesents a command request.
|
||||
type CmdRequest struct {
|
||||
Version int
|
||||
Cmd socks.Command
|
||||
Addr socks.Addr
|
||||
}
|
||||
|
||||
// ParseCmdRequest parses a command request.
|
||||
func ParseCmdRequest(b []byte) (*CmdRequest, error) {
|
||||
if len(b) < 7 {
|
||||
return nil, errors.New("short cmd request")
|
||||
}
|
||||
if b[0] != socks.Version5 {
|
||||
return nil, errors.New("unexpected protocol version")
|
||||
}
|
||||
if socks.Command(b[1]) != socks.CmdConnect {
|
||||
return nil, errors.New("unexpected command")
|
||||
}
|
||||
if b[2] != 0 {
|
||||
return nil, errors.New("non-zero reserved field")
|
||||
}
|
||||
req := &CmdRequest{Version: int(b[0]), Cmd: socks.Command(b[1])}
|
||||
l := 2
|
||||
off := 4
|
||||
switch b[3] {
|
||||
case socks.AddrTypeIPv4:
|
||||
l += net.IPv4len
|
||||
req.Addr.IP = make(net.IP, net.IPv4len)
|
||||
case socks.AddrTypeIPv6:
|
||||
l += net.IPv6len
|
||||
req.Addr.IP = make(net.IP, net.IPv6len)
|
||||
case socks.AddrTypeFQDN:
|
||||
l += int(b[4])
|
||||
off = 5
|
||||
default:
|
||||
return nil, errors.New("unknown address type")
|
||||
}
|
||||
if len(b[off:]) < l {
|
||||
return nil, errors.New("short cmd request")
|
||||
}
|
||||
if req.Addr.IP != nil {
|
||||
copy(req.Addr.IP, b[off:])
|
||||
} else {
|
||||
req.Addr.Name = string(b[off : off+l-2])
|
||||
}
|
||||
req.Addr.Port = int(b[off+l-2])<<8 | int(b[off+l-1])
|
||||
return req, nil
|
||||
}
|
||||
|
||||
// MarshalCmdReply returns a command reply in wire format.
|
||||
func MarshalCmdReply(ver int, reply socks.Reply, a *socks.Addr) ([]byte, error) {
|
||||
b := make([]byte, 4)
|
||||
b[0] = byte(ver)
|
||||
b[1] = byte(reply)
|
||||
if a.Name != "" {
|
||||
if len(a.Name) > 255 {
|
||||
return nil, errors.New("fqdn too long")
|
||||
}
|
||||
b[3] = socks.AddrTypeFQDN
|
||||
b = append(b, byte(len(a.Name)))
|
||||
b = append(b, a.Name...)
|
||||
} else if ip4 := a.IP.To4(); ip4 != nil {
|
||||
b[3] = socks.AddrTypeIPv4
|
||||
b = append(b, ip4...)
|
||||
} else if ip6 := a.IP.To16(); ip6 != nil {
|
||||
b[3] = socks.AddrTypeIPv6
|
||||
b = append(b, ip6...)
|
||||
} else {
|
||||
return nil, errors.New("unknown address type")
|
||||
}
|
||||
b = append(b, byte(a.Port>>8), byte(a.Port))
|
||||
return b, nil
|
||||
}
|
||||
|
||||
// A Server repesents a server for handshake testing.
|
||||
type Server struct {
|
||||
ln net.Listener
|
||||
}
|
||||
|
||||
// Addr rerurns a server address.
|
||||
func (s *Server) Addr() net.Addr {
|
||||
return s.ln.Addr()
|
||||
}
|
||||
|
||||
// TargetAddr returns a fake final destination address.
|
||||
//
|
||||
// The returned address is only valid for testing with Server.
|
||||
func (s *Server) TargetAddr() net.Addr {
|
||||
a := s.ln.Addr()
|
||||
switch a := a.(type) {
|
||||
case *net.TCPAddr:
|
||||
if a.IP.To4() != nil {
|
||||
return &net.TCPAddr{IP: net.IPv4(127, 0, 0, 1), Port: 5963}
|
||||
}
|
||||
if a.IP.To16() != nil && a.IP.To4() == nil {
|
||||
return &net.TCPAddr{IP: net.IPv6loopback, Port: 5963}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Close closes the server.
|
||||
func (s *Server) Close() error {
|
||||
return s.ln.Close()
|
||||
}
|
||||
|
||||
func (s *Server) serve(authFunc, cmdFunc func(io.ReadWriter, []byte) error) {
|
||||
c, err := s.ln.Accept()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer c.Close()
|
||||
go s.serve(authFunc, cmdFunc)
|
||||
b := make([]byte, 512)
|
||||
n, err := c.Read(b)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if err := authFunc(c, b[:n]); err != nil {
|
||||
return
|
||||
}
|
||||
n, err = c.Read(b)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if err := cmdFunc(c, b[:n]); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// NewServer returns a new server.
|
||||
//
|
||||
// The provided authFunc and cmdFunc must parse requests and return
|
||||
// appropriate replies to clients.
|
||||
func NewServer(authFunc, cmdFunc func(io.ReadWriter, []byte) error) (*Server, error) {
|
||||
var err error
|
||||
s := new(Server)
|
||||
s.ln, err = nettest.NewLocalListener("tcp")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
go s.serve(authFunc, cmdFunc)
|
||||
return s, nil
|
||||
}
|
||||
|
||||
// NoAuthRequired handles a no-authentication-required signaling.
|
||||
func NoAuthRequired(rw io.ReadWriter, b []byte) error {
|
||||
req, err := ParseAuthRequest(b)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
b, err = MarshalAuthReply(req.Version, socks.AuthMethodNotRequired)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
n, err := rw.Write(b)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if n != len(b) {
|
||||
return errors.New("short write")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// NoProxyRequired handles a command signaling without constructing a
|
||||
// proxy connection to the final destination.
|
||||
func NoProxyRequired(rw io.ReadWriter, b []byte) error {
|
||||
req, err := ParseCmdRequest(b)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
req.Addr.Port += 1
|
||||
if req.Addr.Name != "" {
|
||||
req.Addr.Name = "boundaddr.doesnotexist"
|
||||
} else if req.Addr.IP.To4() != nil {
|
||||
req.Addr.IP = net.IPv4(127, 0, 0, 1)
|
||||
} else {
|
||||
req.Addr.IP = net.IPv6loopback
|
||||
}
|
||||
b, err = MarshalCmdReply(socks.Version5, socks.StatusSucceeded, &req.Addr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
n, err := rw.Write(b)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if n != len(b) {
|
||||
return errors.New("short write")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
+103
@@ -0,0 +1,103 @@
|
||||
// Copyright 2018 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package sockstest
|
||||
|
||||
import (
|
||||
"net"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"golang.org/x/net/internal/socks"
|
||||
)
|
||||
|
||||
func TestParseAuthRequest(t *testing.T) {
|
||||
for i, tt := range []struct {
|
||||
wire []byte
|
||||
req *AuthRequest
|
||||
}{
|
||||
{
|
||||
[]byte{0x05, 0x00},
|
||||
&AuthRequest{
|
||||
socks.Version5,
|
||||
nil,
|
||||
},
|
||||
},
|
||||
{
|
||||
[]byte{0x05, 0x01, 0xff},
|
||||
&AuthRequest{
|
||||
socks.Version5,
|
||||
[]socks.AuthMethod{
|
||||
socks.AuthMethodNoAcceptableMethods,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
[]byte{0x05, 0x02, 0x00, 0xff},
|
||||
&AuthRequest{
|
||||
socks.Version5,
|
||||
[]socks.AuthMethod{
|
||||
socks.AuthMethodNotRequired,
|
||||
socks.AuthMethodNoAcceptableMethods,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
// corrupted requests
|
||||
{nil, nil},
|
||||
{[]byte{0x00, 0x01}, nil},
|
||||
{[]byte{0x06, 0x00}, nil},
|
||||
{[]byte{0x05, 0x02, 0x00}, nil},
|
||||
} {
|
||||
req, err := ParseAuthRequest(tt.wire)
|
||||
if !reflect.DeepEqual(req, tt.req) {
|
||||
t.Errorf("#%d: got %v, %v; want %v", i, req, err, tt.req)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseCmdRequest(t *testing.T) {
|
||||
for i, tt := range []struct {
|
||||
wire []byte
|
||||
req *CmdRequest
|
||||
}{
|
||||
{
|
||||
[]byte{0x05, 0x01, 0x00, 0x01, 192, 0, 2, 1, 0x17, 0x4b},
|
||||
&CmdRequest{
|
||||
socks.Version5,
|
||||
socks.CmdConnect,
|
||||
socks.Addr{
|
||||
IP: net.IP{192, 0, 2, 1},
|
||||
Port: 5963,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
[]byte{0x05, 0x01, 0x00, 0x03, 0x04, 'F', 'Q', 'D', 'N', 0x17, 0x4b},
|
||||
&CmdRequest{
|
||||
socks.Version5,
|
||||
socks.CmdConnect,
|
||||
socks.Addr{
|
||||
Name: "FQDN",
|
||||
Port: 5963,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
// corrupted requests
|
||||
{nil, nil},
|
||||
{[]byte{0x05}, nil},
|
||||
{[]byte{0x06, 0x01, 0x00, 0x01, 192, 0, 2, 2, 0x17, 0x4b}, nil},
|
||||
{[]byte{0x05, 0x01, 0xff, 0x01, 192, 0, 2, 3}, nil},
|
||||
{[]byte{0x05, 0x01, 0x00, 0x01, 192, 0, 2, 4}, nil},
|
||||
{[]byte{0x05, 0x01, 0x00, 0x03, 0x04, 'F', 'Q', 'D', 'N'}, nil},
|
||||
} {
|
||||
req, err := ParseCmdRequest(tt.wire)
|
||||
if !reflect.DeepEqual(req, tt.req) {
|
||||
t.Errorf("#%d: got %v, %v; want %v", i, req, err, tt.req)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
+31
-5
@@ -14,27 +14,53 @@ import (
|
||||
// LimitListener returns a Listener that accepts at most n simultaneous
|
||||
// connections from the provided Listener.
|
||||
func LimitListener(l net.Listener, n int) net.Listener {
|
||||
return &limitListener{l, make(chan struct{}, n)}
|
||||
return &limitListener{
|
||||
Listener: l,
|
||||
sem: make(chan struct{}, n),
|
||||
done: make(chan struct{}),
|
||||
}
|
||||
}
|
||||
|
||||
type limitListener struct {
|
||||
net.Listener
|
||||
sem chan struct{}
|
||||
sem chan struct{}
|
||||
closeOnce sync.Once // ensures the done chan is only closed once
|
||||
done chan struct{} // no values sent; closed when Close is called
|
||||
}
|
||||
|
||||
func (l *limitListener) acquire() { l.sem <- struct{}{} }
|
||||
// acquire acquires the limiting semaphore. Returns true if successfully
|
||||
// accquired, false if the listener is closed and the semaphore is not
|
||||
// acquired.
|
||||
func (l *limitListener) acquire() bool {
|
||||
select {
|
||||
case <-l.done:
|
||||
return false
|
||||
case l.sem <- struct{}{}:
|
||||
return true
|
||||
}
|
||||
}
|
||||
func (l *limitListener) release() { <-l.sem }
|
||||
|
||||
func (l *limitListener) Accept() (net.Conn, error) {
|
||||
l.acquire()
|
||||
acquired := l.acquire()
|
||||
// If the semaphore isn't acquired because the listener was closed, expect
|
||||
// that this call to accept won't block, but immediately return an error.
|
||||
c, err := l.Listener.Accept()
|
||||
if err != nil {
|
||||
l.release()
|
||||
if acquired {
|
||||
l.release()
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
return &limitListenerConn{Conn: c, release: l.release}, nil
|
||||
}
|
||||
|
||||
func (l *limitListener) Close() error {
|
||||
err := l.Listener.Close()
|
||||
l.closeOnce.Do(func() { close(l.done) })
|
||||
return err
|
||||
}
|
||||
|
||||
type limitListenerConn struct {
|
||||
net.Conn
|
||||
releaseOnce sync.Once
|
||||
|
||||
+46
@@ -99,3 +99,49 @@ func TestLimitListenerError(t *testing.T) {
|
||||
t.Fatal("timeout. deadlock?")
|
||||
}
|
||||
}
|
||||
|
||||
func TestLimitListenerClose(t *testing.T) {
|
||||
ln, err := net.Listen("tcp", "127.0.0.1:0")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer ln.Close()
|
||||
ln = LimitListener(ln, 1)
|
||||
|
||||
doneCh := make(chan struct{})
|
||||
defer close(doneCh)
|
||||
go func() {
|
||||
c, err := net.Dial("tcp", ln.Addr().String())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer c.Close()
|
||||
<-doneCh
|
||||
}()
|
||||
|
||||
c, err := ln.Accept()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer c.Close()
|
||||
|
||||
acceptDone := make(chan struct{})
|
||||
go func() {
|
||||
c, err := ln.Accept()
|
||||
if err == nil {
|
||||
c.Close()
|
||||
t.Errorf("Unexpected successful Accept()")
|
||||
}
|
||||
close(acceptDone)
|
||||
}()
|
||||
|
||||
// Wait a tiny bit to ensure the Accept() is blocking.
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
ln.Close()
|
||||
|
||||
select {
|
||||
case <-acceptDone:
|
||||
case <-time.After(5 * time.Second):
|
||||
t.Fatalf("Accept() still blocking")
|
||||
}
|
||||
}
|
||||
|
||||
+20
-112
@@ -7,14 +7,12 @@ package proxy
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/url"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
"golang.org/x/net/internal/sockstest"
|
||||
)
|
||||
|
||||
type proxyFromEnvTest struct {
|
||||
@@ -73,131 +71,41 @@ func TestFromEnvironment(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestFromURL(t *testing.T) {
|
||||
endSystem, err := net.Listen("tcp", "127.0.0.1:0")
|
||||
ss, err := sockstest.NewServer(sockstest.NoAuthRequired, sockstest.NoProxyRequired)
|
||||
if err != nil {
|
||||
t.Fatalf("net.Listen failed: %v", err)
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer endSystem.Close()
|
||||
gateway, err := net.Listen("tcp", "127.0.0.1:0")
|
||||
defer ss.Close()
|
||||
url, err := url.Parse("socks5://user:password@" + ss.Addr().String())
|
||||
if err != nil {
|
||||
t.Fatalf("net.Listen failed: %v", err)
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer gateway.Close()
|
||||
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(1)
|
||||
go socks5Gateway(t, gateway, endSystem, socks5Domain, &wg)
|
||||
|
||||
url, err := url.Parse("socks5://user:password@" + gateway.Addr().String())
|
||||
proxy, err := FromURL(url, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("url.Parse failed: %v", err)
|
||||
t.Fatal(err)
|
||||
}
|
||||
proxy, err := FromURL(url, Direct)
|
||||
c, err := proxy.Dial("tcp", "fqdn.doesnotexist:5963")
|
||||
if err != nil {
|
||||
t.Fatalf("FromURL failed: %v", err)
|
||||
t.Fatal(err)
|
||||
}
|
||||
_, port, err := net.SplitHostPort(endSystem.Addr().String())
|
||||
if err != nil {
|
||||
t.Fatalf("net.SplitHostPort failed: %v", err)
|
||||
}
|
||||
if c, err := proxy.Dial("tcp", "localhost:"+port); err != nil {
|
||||
t.Fatalf("FromURL.Dial failed: %v", err)
|
||||
} else {
|
||||
c.Close()
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
c.Close()
|
||||
}
|
||||
|
||||
func TestSOCKS5(t *testing.T) {
|
||||
endSystem, err := net.Listen("tcp", "127.0.0.1:0")
|
||||
ss, err := sockstest.NewServer(sockstest.NoAuthRequired, sockstest.NoProxyRequired)
|
||||
if err != nil {
|
||||
t.Fatalf("net.Listen failed: %v", err)
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer endSystem.Close()
|
||||
gateway, err := net.Listen("tcp", "127.0.0.1:0")
|
||||
defer ss.Close()
|
||||
proxy, err := SOCKS5("tcp", ss.Addr().String(), nil, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("net.Listen failed: %v", err)
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer gateway.Close()
|
||||
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(1)
|
||||
go socks5Gateway(t, gateway, endSystem, socks5IP4, &wg)
|
||||
|
||||
proxy, err := SOCKS5("tcp", gateway.Addr().String(), nil, Direct)
|
||||
c, err := proxy.Dial("tcp", ss.TargetAddr().String())
|
||||
if err != nil {
|
||||
t.Fatalf("SOCKS5 failed: %v", err)
|
||||
}
|
||||
if c, err := proxy.Dial("tcp", endSystem.Addr().String()); err != nil {
|
||||
t.Fatalf("SOCKS5.Dial failed: %v", err)
|
||||
} else {
|
||||
c.Close()
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
func socks5Gateway(t *testing.T, gateway, endSystem net.Listener, typ byte, wg *sync.WaitGroup) {
|
||||
defer wg.Done()
|
||||
|
||||
c, err := gateway.Accept()
|
||||
if err != nil {
|
||||
t.Errorf("net.Listener.Accept failed: %v", err)
|
||||
return
|
||||
}
|
||||
defer c.Close()
|
||||
|
||||
b := make([]byte, 32)
|
||||
var n int
|
||||
if typ == socks5Domain {
|
||||
n = 4
|
||||
} else {
|
||||
n = 3
|
||||
}
|
||||
if _, err := io.ReadFull(c, b[:n]); err != nil {
|
||||
t.Errorf("io.ReadFull failed: %v", err)
|
||||
return
|
||||
}
|
||||
if _, err := c.Write([]byte{socks5Version, socks5AuthNone}); err != nil {
|
||||
t.Errorf("net.Conn.Write failed: %v", err)
|
||||
return
|
||||
}
|
||||
if typ == socks5Domain {
|
||||
n = 16
|
||||
} else {
|
||||
n = 10
|
||||
}
|
||||
if _, err := io.ReadFull(c, b[:n]); err != nil {
|
||||
t.Errorf("io.ReadFull failed: %v", err)
|
||||
return
|
||||
}
|
||||
if b[0] != socks5Version || b[1] != socks5Connect || b[2] != 0x00 || b[3] != typ {
|
||||
t.Errorf("got an unexpected packet: %#02x %#02x %#02x %#02x", b[0], b[1], b[2], b[3])
|
||||
return
|
||||
}
|
||||
if typ == socks5Domain {
|
||||
copy(b[:5], []byte{socks5Version, 0x00, 0x00, socks5Domain, 9})
|
||||
b = append(b, []byte("localhost")...)
|
||||
} else {
|
||||
copy(b[:4], []byte{socks5Version, 0x00, 0x00, socks5IP4})
|
||||
}
|
||||
host, port, err := net.SplitHostPort(endSystem.Addr().String())
|
||||
if err != nil {
|
||||
t.Errorf("net.SplitHostPort failed: %v", err)
|
||||
return
|
||||
}
|
||||
b = append(b, []byte(net.ParseIP(host).To4())...)
|
||||
p, err := strconv.Atoi(port)
|
||||
if err != nil {
|
||||
t.Errorf("strconv.Atoi failed: %v", err)
|
||||
return
|
||||
}
|
||||
b = append(b, []byte{byte(p >> 8), byte(p)}...)
|
||||
if _, err := c.Write(b); err != nil {
|
||||
t.Errorf("net.Conn.Write failed: %v", err)
|
||||
return
|
||||
t.Fatal(err)
|
||||
}
|
||||
c.Close()
|
||||
}
|
||||
|
||||
func ResetProxyEnv() {
|
||||
|
||||
+22
-200
@@ -5,210 +5,32 @@
|
||||
package proxy
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io"
|
||||
"context"
|
||||
"net"
|
||||
"strconv"
|
||||
|
||||
"golang.org/x/net/internal/socks"
|
||||
)
|
||||
|
||||
// SOCKS5 returns a Dialer that makes SOCKSv5 connections to the given address
|
||||
// with an optional username and password. See RFC 1928 and RFC 1929.
|
||||
func SOCKS5(network, addr string, auth *Auth, forward Dialer) (Dialer, error) {
|
||||
s := &socks5{
|
||||
network: network,
|
||||
addr: addr,
|
||||
forward: forward,
|
||||
// SOCKS5 returns a Dialer that makes SOCKSv5 connections to the given
|
||||
// address with an optional username and password.
|
||||
// See RFC 1928 and RFC 1929.
|
||||
func SOCKS5(network, address string, auth *Auth, forward Dialer) (Dialer, error) {
|
||||
d := socks.NewDialer(network, address)
|
||||
if forward != nil {
|
||||
d.ProxyDial = func(_ context.Context, network string, address string) (net.Conn, error) {
|
||||
return forward.Dial(network, address)
|
||||
}
|
||||
}
|
||||
if auth != nil {
|
||||
s.user = auth.User
|
||||
s.password = auth.Password
|
||||
up := socks.UsernamePassword{
|
||||
Username: auth.User,
|
||||
Password: auth.Password,
|
||||
}
|
||||
d.AuthMethods = []socks.AuthMethod{
|
||||
socks.AuthMethodNotRequired,
|
||||
socks.AuthMethodUsernamePassword,
|
||||
}
|
||||
d.Authenticate = up.Authenticate
|
||||
}
|
||||
|
||||
return s, nil
|
||||
}
|
||||
|
||||
type socks5 struct {
|
||||
user, password string
|
||||
network, addr string
|
||||
forward Dialer
|
||||
}
|
||||
|
||||
const socks5Version = 5
|
||||
|
||||
const (
|
||||
socks5AuthNone = 0
|
||||
socks5AuthPassword = 2
|
||||
)
|
||||
|
||||
const socks5Connect = 1
|
||||
|
||||
const (
|
||||
socks5IP4 = 1
|
||||
socks5Domain = 3
|
||||
socks5IP6 = 4
|
||||
)
|
||||
|
||||
var socks5Errors = []string{
|
||||
"",
|
||||
"general failure",
|
||||
"connection forbidden",
|
||||
"network unreachable",
|
||||
"host unreachable",
|
||||
"connection refused",
|
||||
"TTL expired",
|
||||
"command not supported",
|
||||
"address type not supported",
|
||||
}
|
||||
|
||||
// Dial connects to the address addr on the given network via the SOCKS5 proxy.
|
||||
func (s *socks5) Dial(network, addr string) (net.Conn, error) {
|
||||
switch network {
|
||||
case "tcp", "tcp6", "tcp4":
|
||||
default:
|
||||
return nil, errors.New("proxy: no support for SOCKS5 proxy connections of type " + network)
|
||||
}
|
||||
|
||||
conn, err := s.forward.Dial(s.network, s.addr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := s.connect(conn, addr); err != nil {
|
||||
conn.Close()
|
||||
return nil, err
|
||||
}
|
||||
return conn, nil
|
||||
}
|
||||
|
||||
// connect takes an existing connection to a socks5 proxy server,
|
||||
// and commands the server to extend that connection to target,
|
||||
// which must be a canonical address with a host and port.
|
||||
func (s *socks5) connect(conn net.Conn, target string) error {
|
||||
host, portStr, err := net.SplitHostPort(target)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
port, err := strconv.Atoi(portStr)
|
||||
if err != nil {
|
||||
return errors.New("proxy: failed to parse port number: " + portStr)
|
||||
}
|
||||
if port < 1 || port > 0xffff {
|
||||
return errors.New("proxy: port number out of range: " + portStr)
|
||||
}
|
||||
|
||||
// the size here is just an estimate
|
||||
buf := make([]byte, 0, 6+len(host))
|
||||
|
||||
buf = append(buf, socks5Version)
|
||||
if len(s.user) > 0 && len(s.user) < 256 && len(s.password) < 256 {
|
||||
buf = append(buf, 2 /* num auth methods */, socks5AuthNone, socks5AuthPassword)
|
||||
} else {
|
||||
buf = append(buf, 1 /* num auth methods */, socks5AuthNone)
|
||||
}
|
||||
|
||||
if _, err := conn.Write(buf); err != nil {
|
||||
return errors.New("proxy: failed to write greeting to SOCKS5 proxy at " + s.addr + ": " + err.Error())
|
||||
}
|
||||
|
||||
if _, err := io.ReadFull(conn, buf[:2]); err != nil {
|
||||
return errors.New("proxy: failed to read greeting from SOCKS5 proxy at " + s.addr + ": " + err.Error())
|
||||
}
|
||||
if buf[0] != 5 {
|
||||
return errors.New("proxy: SOCKS5 proxy at " + s.addr + " has unexpected version " + strconv.Itoa(int(buf[0])))
|
||||
}
|
||||
if buf[1] == 0xff {
|
||||
return errors.New("proxy: SOCKS5 proxy at " + s.addr + " requires authentication")
|
||||
}
|
||||
|
||||
// See RFC 1929
|
||||
if buf[1] == socks5AuthPassword {
|
||||
buf = buf[:0]
|
||||
buf = append(buf, 1 /* password protocol version */)
|
||||
buf = append(buf, uint8(len(s.user)))
|
||||
buf = append(buf, s.user...)
|
||||
buf = append(buf, uint8(len(s.password)))
|
||||
buf = append(buf, s.password...)
|
||||
|
||||
if _, err := conn.Write(buf); err != nil {
|
||||
return errors.New("proxy: failed to write authentication request to SOCKS5 proxy at " + s.addr + ": " + err.Error())
|
||||
}
|
||||
|
||||
if _, err := io.ReadFull(conn, buf[:2]); err != nil {
|
||||
return errors.New("proxy: failed to read authentication reply from SOCKS5 proxy at " + s.addr + ": " + err.Error())
|
||||
}
|
||||
|
||||
if buf[1] != 0 {
|
||||
return errors.New("proxy: SOCKS5 proxy at " + s.addr + " rejected username/password")
|
||||
}
|
||||
}
|
||||
|
||||
buf = buf[:0]
|
||||
buf = append(buf, socks5Version, socks5Connect, 0 /* reserved */)
|
||||
|
||||
if ip := net.ParseIP(host); ip != nil {
|
||||
if ip4 := ip.To4(); ip4 != nil {
|
||||
buf = append(buf, socks5IP4)
|
||||
ip = ip4
|
||||
} else {
|
||||
buf = append(buf, socks5IP6)
|
||||
}
|
||||
buf = append(buf, ip...)
|
||||
} else {
|
||||
if len(host) > 255 {
|
||||
return errors.New("proxy: destination host name too long: " + host)
|
||||
}
|
||||
buf = append(buf, socks5Domain)
|
||||
buf = append(buf, byte(len(host)))
|
||||
buf = append(buf, host...)
|
||||
}
|
||||
buf = append(buf, byte(port>>8), byte(port))
|
||||
|
||||
if _, err := conn.Write(buf); err != nil {
|
||||
return errors.New("proxy: failed to write connect request to SOCKS5 proxy at " + s.addr + ": " + err.Error())
|
||||
}
|
||||
|
||||
if _, err := io.ReadFull(conn, buf[:4]); err != nil {
|
||||
return errors.New("proxy: failed to read connect reply from SOCKS5 proxy at " + s.addr + ": " + err.Error())
|
||||
}
|
||||
|
||||
failure := "unknown error"
|
||||
if int(buf[1]) < len(socks5Errors) {
|
||||
failure = socks5Errors[buf[1]]
|
||||
}
|
||||
|
||||
if len(failure) > 0 {
|
||||
return errors.New("proxy: SOCKS5 proxy at " + s.addr + " failed to connect: " + failure)
|
||||
}
|
||||
|
||||
bytesToDiscard := 0
|
||||
switch buf[3] {
|
||||
case socks5IP4:
|
||||
bytesToDiscard = net.IPv4len
|
||||
case socks5IP6:
|
||||
bytesToDiscard = net.IPv6len
|
||||
case socks5Domain:
|
||||
_, err := io.ReadFull(conn, buf[:1])
|
||||
if err != nil {
|
||||
return errors.New("proxy: failed to read domain length from SOCKS5 proxy at " + s.addr + ": " + err.Error())
|
||||
}
|
||||
bytesToDiscard = int(buf[0])
|
||||
default:
|
||||
return errors.New("proxy: got unknown address type " + strconv.Itoa(int(buf[3])) + " from SOCKS5 proxy at " + s.addr)
|
||||
}
|
||||
|
||||
if cap(buf) < bytesToDiscard {
|
||||
buf = make([]byte, bytesToDiscard)
|
||||
} else {
|
||||
buf = buf[:bytesToDiscard]
|
||||
}
|
||||
if _, err := io.ReadFull(conn, buf); err != nil {
|
||||
return errors.New("proxy: failed to read address from SOCKS5 proxy at " + s.addr + ": " + err.Error())
|
||||
}
|
||||
|
||||
// Also need to discard the port number
|
||||
if _, err := io.ReadFull(conn, buf[:2]); err != nil {
|
||||
return errors.New("proxy: failed to read port from SOCKS5 proxy at " + s.addr + ": " + err.Error())
|
||||
}
|
||||
|
||||
return nil
|
||||
return d, nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user