-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
implement parsing of the ADDRESS_ASSIGN capsule
- Loading branch information
1 parent
595ebd6
commit 20d698f
Showing
4 changed files
with
292 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,80 @@ | ||
package masque | ||
|
||
import ( | ||
"errors" | ||
"fmt" | ||
"io" | ||
"net/netip" | ||
|
||
"github.com/quic-go/quic-go/http3" | ||
"github.com/quic-go/quic-go/quicvarint" | ||
) | ||
|
||
const capsuleTypeAddressAssign http3.CapsuleType = 1 | ||
|
||
// addressAssignCapsule represents an ADDRESS_ASSIGN capsule | ||
type addressAssignCapsule struct { | ||
AssignedAddresses []AssignedAddress | ||
} | ||
|
||
// AssignedAddress represents an Assigned Address within an ADDRESS_ASSIGN capsule | ||
type AssignedAddress struct { | ||
RequestID uint64 | ||
IPPrefix netip.Prefix | ||
} | ||
|
||
func parseAddressAssignCapsule(r io.Reader) (*addressAssignCapsule, error) { | ||
var assignedAddresses []AssignedAddress | ||
for { | ||
addr, err := parseAssignedAddress(r) | ||
if err != nil { | ||
if err == io.EOF { | ||
break | ||
} | ||
return nil, err | ||
} | ||
assignedAddresses = append(assignedAddresses, addr) | ||
} | ||
return &addressAssignCapsule{AssignedAddresses: assignedAddresses}, nil | ||
} | ||
|
||
func parseAssignedAddress(r io.Reader) (AssignedAddress, error) { | ||
vr := quicvarint.NewReader(r) | ||
requestID, err := quicvarint.Read(vr) | ||
if err != nil { | ||
return AssignedAddress{}, err | ||
} | ||
ipVersion, err := vr.ReadByte() | ||
if err != nil { | ||
return AssignedAddress{}, err | ||
} | ||
var ip netip.Addr | ||
switch ipVersion { | ||
case 4: | ||
var ipv4 [4]byte | ||
if _, err := io.ReadFull(r, ipv4[:]); err != nil { | ||
return AssignedAddress{}, err | ||
} | ||
ip = netip.AddrFrom4(ipv4) | ||
case 6: | ||
var ipv6 [16]byte | ||
if _, err := io.ReadFull(r, ipv6[:]); err != nil { | ||
return AssignedAddress{}, err | ||
} | ||
ip = netip.AddrFrom16(ipv6) | ||
default: | ||
return AssignedAddress{}, fmt.Errorf("invalid IP version: %d", ipVersion) | ||
} | ||
prefixLen, err := vr.ReadByte() | ||
if err != nil { | ||
return AssignedAddress{}, err | ||
} | ||
if int(prefixLen) > ip.BitLen() { | ||
return AssignedAddress{}, fmt.Errorf("prefix length %d exceeds IP address length (%d)", prefixLen, ip.BitLen()) | ||
} | ||
prefix := netip.PrefixFrom(ip, int(prefixLen)) | ||
if prefix != prefix.Masked() { | ||
return AssignedAddress{}, errors.New("lower bits not covered by prefix length are not all zero") | ||
} | ||
return AssignedAddress{RequestID: requestID, IPPrefix: prefix}, nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,119 @@ | ||
package masque | ||
|
||
import ( | ||
"bytes" | ||
"io" | ||
"net/netip" | ||
"testing" | ||
|
||
"github.com/quic-go/quic-go/http3" | ||
"github.com/quic-go/quic-go/quicvarint" | ||
|
||
"github.com/stretchr/testify/require" | ||
) | ||
|
||
func TestParseAddressAssignCapsule(t *testing.T) { | ||
addr1 := quicvarint.Append(nil, 1337) // Request ID | ||
addr1 = append(addr1, 4) // IPv4 | ||
addr1 = append(addr1, netip.AddrFrom4([4]byte{1, 2, 3, 0}).AsSlice()...) | ||
addr1 = append(addr1, 24) // IP Prefix Length | ||
addr2 := quicvarint.Append(nil, 1338) // Request ID | ||
addr2 = append(addr2, 6) // IPv6 | ||
addr2 = append(addr2, netip.MustParseAddr("2001:db8::1").AsSlice()...) | ||
addr2 = append(addr2, 128) // IP Prefix Length | ||
|
||
data := quicvarint.Append(nil, uint64(capsuleTypeAddressAssign)) | ||
data = quicvarint.Append(data, uint64(len(addr1)+len(addr2))) // Length | ||
data = append(data, addr1...) | ||
data = append(data, addr2...) | ||
|
||
r := bytes.NewReader(data) | ||
typ, cr, err := http3.ParseCapsule(r) | ||
require.NoError(t, err) | ||
require.Equal(t, capsuleTypeAddressAssign, typ) | ||
capsule, err := parseAddressAssignCapsule(cr) | ||
require.NoError(t, err) | ||
require.Equal(t, | ||
[]AssignedAddress{ | ||
{RequestID: 1337, IPPrefix: netip.MustParsePrefix("1.2.3.0/24")}, | ||
{RequestID: 1338, IPPrefix: netip.MustParsePrefix("2001:db8::1/128")}, | ||
}, | ||
capsule.AssignedAddresses, | ||
) | ||
require.Zero(t, r.Len()) | ||
} | ||
|
||
func TestParseAddressAssignCapsuleInvalid(t *testing.T) { | ||
t.Run("invalid IP version", func(t *testing.T) { | ||
addr1 := quicvarint.Append(nil, 1337) // Request ID | ||
addr1 = append(addr1, 5) // Invalid IP version (not 4 or 6) | ||
addr1 = append(addr1, netip.AddrFrom4([4]byte{1, 2, 3, 4}).AsSlice()...) | ||
addr1 = append(addr1, 32) // IP Prefix Length | ||
data := quicvarint.Append(nil, uint64(capsuleTypeAddressAssign)) | ||
data = quicvarint.Append(data, uint64(len(addr1))) // Length | ||
data = append(data, addr1...) | ||
|
||
_, cr, err := http3.ParseCapsule(bytes.NewReader(data)) | ||
require.NoError(t, err) | ||
_, err = parseAddressAssignCapsule(cr) | ||
require.ErrorContains(t, err, "invalid IP version: 5") | ||
}) | ||
|
||
t.Run("invalid prefix length", func(t *testing.T) { | ||
addr1 := quicvarint.Append(nil, 1337) // Request ID | ||
addr1 = append(addr1, 4) // IPv4 | ||
addr1 = append(addr1, netip.AddrFrom4([4]byte{1, 2, 3, 4}).AsSlice()...) | ||
addr1 = append(addr1, 33) // too long IP Prefix Length | ||
data := quicvarint.Append(nil, uint64(capsuleTypeAddressAssign)) | ||
data = quicvarint.Append(data, uint64(len(addr1))) // Length | ||
data = append(data, addr1...) | ||
|
||
_, cr, err := http3.ParseCapsule(bytes.NewReader(data)) | ||
require.NoError(t, err) | ||
_, err = parseAddressAssignCapsule(cr) | ||
require.ErrorContains(t, err, "prefix length 33 exceeds IP address length (32)") | ||
}) | ||
|
||
t.Run("lower bits not covered by prefix length are not all zero", func(t *testing.T) { | ||
addr1 := quicvarint.Append(nil, 1337) // Request ID | ||
addr1 = append(addr1, 4) // IPv4 | ||
addr1 = append(addr1, netip.AddrFrom4([4]byte{1, 2, 3, 4}).AsSlice()...) // non-zero lower bits | ||
addr1 = append(addr1, 28) // IP Prefix Length | ||
data := quicvarint.Append(nil, uint64(capsuleTypeAddressAssign)) | ||
data = quicvarint.Append(data, uint64(len(addr1))) // Length | ||
data = append(data, addr1...) | ||
|
||
_, cr, err := http3.ParseCapsule(bytes.NewReader(data)) | ||
require.NoError(t, err) | ||
_, err = parseAddressAssignCapsule(cr) | ||
require.ErrorContains(t, err, "lower bits not covered by prefix length are not all zero") | ||
}) | ||
|
||
t.Run("incomplete capsule", func(t *testing.T) { | ||
addr1 := quicvarint.Append(nil, 1337) // Request ID | ||
addr1 = append(addr1, 4) // IPv4 | ||
addr1 = append(addr1, netip.AddrFrom4([4]byte{1, 2, 3, 4}).AsSlice()...) | ||
addr1 = append(addr1, 32) // IP Prefix Length | ||
data := quicvarint.Append(nil, uint64(capsuleTypeAddressAssign)) | ||
data = quicvarint.Append(data, uint64(len(addr1))) // Length | ||
data = append(data, addr1...) | ||
|
||
_, cr, err := http3.ParseCapsule(bytes.NewReader(data)) | ||
require.NoError(t, err) | ||
_, err = parseAddressAssignCapsule(cr) | ||
require.NoError(t, err) | ||
for i := range data { | ||
_, cr, err := http3.ParseCapsule(bytes.NewReader(data[:i])) | ||
if err != nil { | ||
if i == 0 { | ||
require.ErrorIs(t, err, io.EOF) | ||
} else { | ||
require.ErrorIs(t, err, io.ErrUnexpectedEOF) | ||
} | ||
continue | ||
} | ||
_, err = parseAddressAssignCapsule(cr) | ||
require.ErrorIs(t, err, io.ErrUnexpectedEOF) | ||
} | ||
}) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,28 @@ | ||
module github.com/quic-go/connect-ip-go | ||
|
||
go 1.22 | ||
|
||
require ( | ||
github.com/quic-go/quic-go v0.47.1-0.20241002141227-b2233591adc7 | ||
github.com/stretchr/testify v1.9.0 | ||
) | ||
|
||
require ( | ||
github.com/davecgh/go-spew v1.1.1 // indirect | ||
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect | ||
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 // indirect | ||
github.com/kr/pretty v0.3.1 // indirect | ||
github.com/onsi/ginkgo/v2 v2.9.5 // indirect | ||
github.com/pmezard/go-difflib v1.0.0 // indirect | ||
github.com/quic-go/qpack v0.5.1 // indirect | ||
go.uber.org/mock v0.4.0 // indirect | ||
golang.org/x/crypto v0.26.0 // indirect | ||
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 // indirect | ||
golang.org/x/mod v0.17.0 // indirect | ||
golang.org/x/net v0.28.0 // indirect | ||
golang.org/x/sys v0.23.0 // indirect | ||
golang.org/x/text v0.17.0 // indirect | ||
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect | ||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect | ||
gopkg.in/yaml.v3 v3.0.1 // indirect | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,68 @@ | ||
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= | ||
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= | ||
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= | ||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= | ||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= | ||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= | ||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= | ||
github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ= | ||
github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= | ||
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= | ||
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls= | ||
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= | ||
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= | ||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= | ||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= | ||
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 h1:yAJXTCF9TqKcTiHJAE8dj7HMvPfh66eeA2JYW7eFpSE= | ||
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= | ||
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= | ||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= | ||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= | ||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= | ||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= | ||
github.com/onsi/ginkgo/v2 v2.9.5 h1:+6Hr4uxzP4XIUyAkg61dWBw8lb/gc4/X5luuxN/EC+Q= | ||
github.com/onsi/ginkgo/v2 v2.9.5/go.mod h1:tvAoo1QUJwNEU2ITftXTpR7R1RbCzoZUOs3RonqW57k= | ||
github.com/onsi/gomega v1.27.6 h1:ENqfyGeS5AX/rlXDd/ETokDz93u0YufY1Pgxuy/PvWE= | ||
github.com/onsi/gomega v1.27.6/go.mod h1:PIQNjfQwkP3aQAH7lf7j87O/5FiNr+ZR8+ipb+qQlhg= | ||
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= | ||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= | ||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= | ||
github.com/quic-go/qpack v0.5.1 h1:giqksBPnT/HDtZ6VhtFKgoLOWmlyo9Ei6u9PqzIMbhI= | ||
github.com/quic-go/qpack v0.5.1/go.mod h1:+PC4XFrEskIVkcLzpEkbLqq1uCoxPhQuvK5rH1ZgaEg= | ||
github.com/quic-go/quic-go v0.47.1-0.20241002141227-b2233591adc7 h1:/yqMifKM4Z946YBFFUiysDy5jUlCxMNuCVfxArX/eMg= | ||
github.com/quic-go/quic-go v0.47.1-0.20241002141227-b2233591adc7/go.mod h1:yBgs3rWBOADpga7F+jJsb6Ybg1LSYiQvwWlLX+/6HMs= | ||
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= | ||
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= | ||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= | ||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= | ||
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= | ||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= | ||
go.uber.org/mock v0.4.0 h1:VcM4ZOtdbR4f6VXfiOpwpVJDL6lCReaZ6mw31wqh7KU= | ||
go.uber.org/mock v0.4.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc= | ||
golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw= | ||
golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54= | ||
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 h1:vr/HnozRka3pE4EsMEg1lgkXJkTFJCVUX+S/ZT6wYzM= | ||
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842/go.mod h1:XtvwrStGgqGPLc4cjQfWqZHG1YFdYs6swckp8vpsjnc= | ||
golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA= | ||
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= | ||
golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE= | ||
golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg= | ||
golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= | ||
golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= | ||
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | ||
golang.org/x/sys v0.23.0 h1:YfKFowiIMvtgl1UERQoTPPToxltDeZfbj4H7dVUCwmM= | ||
golang.org/x/sys v0.23.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= | ||
golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc= | ||
golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= | ||
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= | ||
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= | ||
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg= | ||
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= | ||
google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= | ||
google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= | ||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= | ||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= | ||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= | ||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= | ||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= | ||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= |