| // Copyright 2020 The gVisor Authors. |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| package header_test |
| |
| import ( |
| "testing" |
| |
| "github.com/google/go-cmp/cmp" |
| "gvisor.dev/gvisor/pkg/tcpip/buffer" |
| "gvisor.dev/gvisor/pkg/tcpip/header" |
| ) |
| |
| func TestIPv4OptionsSerializer(t *testing.T) { |
| optCases := []struct { |
| name string |
| option []header.IPv4SerializableOption |
| expect []byte |
| }{ |
| { |
| name: "NOP", |
| option: []header.IPv4SerializableOption{ |
| &header.IPv4SerializableNOPOption{}, |
| }, |
| expect: []byte{1, 0, 0, 0}, |
| }, |
| { |
| name: "ListEnd", |
| option: []header.IPv4SerializableOption{ |
| &header.IPv4SerializableListEndOption{}, |
| }, |
| expect: []byte{0, 0, 0, 0}, |
| }, |
| { |
| name: "RouterAlert", |
| option: []header.IPv4SerializableOption{ |
| &header.IPv4SerializableRouterAlertOption{}, |
| }, |
| expect: []byte{148, 4, 0, 0}, |
| }, { |
| name: "NOP and RouterAlert", |
| option: []header.IPv4SerializableOption{ |
| &header.IPv4SerializableNOPOption{}, |
| &header.IPv4SerializableRouterAlertOption{}, |
| }, |
| expect: []byte{1, 148, 4, 0, 0, 0, 0, 0}, |
| }, |
| } |
| |
| for _, opt := range optCases { |
| t.Run(opt.name, func(t *testing.T) { |
| s := header.IPv4OptionsSerializer(opt.option) |
| l := s.Length() |
| if got := len(opt.expect); got != int(l) { |
| t.Fatalf("s.Length() = %d, want = %d", got, l) |
| } |
| b := make([]byte, l) |
| for i := range b { |
| // Fill the buffer with full bytes to ensure padding is being set |
| // correctly. |
| b[i] = 0xFF |
| } |
| if serializedLength := s.Serialize(b); serializedLength != l { |
| t.Fatalf("s.Serialize(_) = %d, want %d", serializedLength, l) |
| } |
| if diff := cmp.Diff(opt.expect, b); diff != "" { |
| t.Errorf("mismatched serialized option (-want +got):\n%s", diff) |
| } |
| }) |
| } |
| } |
| |
| // TestIPv4Encode checks that ipv4.Encode correctly fills out the requested |
| // fields when options are supplied. |
| func TestIPv4EncodeOptions(t *testing.T) { |
| tests := []struct { |
| name string |
| numberOfNops int |
| encodedOptions header.IPv4Options // reply should look like this |
| wantIHL int |
| }{ |
| { |
| name: "valid no options", |
| wantIHL: header.IPv4MinimumSize, |
| }, |
| { |
| name: "one byte options", |
| numberOfNops: 1, |
| encodedOptions: header.IPv4Options{1, 0, 0, 0}, |
| wantIHL: header.IPv4MinimumSize + 4, |
| }, |
| { |
| name: "two byte options", |
| numberOfNops: 2, |
| encodedOptions: header.IPv4Options{1, 1, 0, 0}, |
| wantIHL: header.IPv4MinimumSize + 4, |
| }, |
| { |
| name: "three byte options", |
| numberOfNops: 3, |
| encodedOptions: header.IPv4Options{1, 1, 1, 0}, |
| wantIHL: header.IPv4MinimumSize + 4, |
| }, |
| { |
| name: "four byte options", |
| numberOfNops: 4, |
| encodedOptions: header.IPv4Options{1, 1, 1, 1}, |
| wantIHL: header.IPv4MinimumSize + 4, |
| }, |
| { |
| name: "five byte options", |
| numberOfNops: 5, |
| encodedOptions: header.IPv4Options{1, 1, 1, 1, 1, 0, 0, 0}, |
| wantIHL: header.IPv4MinimumSize + 8, |
| }, |
| { |
| name: "thirty nine byte options", |
| numberOfNops: 39, |
| encodedOptions: header.IPv4Options{ |
| 1, 1, 1, 1, 1, 1, 1, 1, |
| 1, 1, 1, 1, 1, 1, 1, 1, |
| 1, 1, 1, 1, 1, 1, 1, 1, |
| 1, 1, 1, 1, 1, 1, 1, 1, |
| 1, 1, 1, 1, 1, 1, 1, 0, |
| }, |
| wantIHL: header.IPv4MinimumSize + 40, |
| }, |
| } |
| for _, test := range tests { |
| t.Run(test.name, func(t *testing.T) { |
| serializeOpts := header.IPv4OptionsSerializer(make([]header.IPv4SerializableOption, test.numberOfNops)) |
| for i := range serializeOpts { |
| serializeOpts[i] = &header.IPv4SerializableNOPOption{} |
| } |
| paddedOptionLength := serializeOpts.Length() |
| ipHeaderLength := int(header.IPv4MinimumSize + paddedOptionLength) |
| if ipHeaderLength > header.IPv4MaximumHeaderSize { |
| t.Fatalf("IP header length too large: got = %d, want <= %d ", ipHeaderLength, header.IPv4MaximumHeaderSize) |
| } |
| totalLen := uint16(ipHeaderLength) |
| hdr := buffer.NewPrependable(int(totalLen)) |
| ip := header.IPv4(hdr.Prepend(ipHeaderLength)) |
| // To check the padding works, poison the last byte of the options space. |
| if paddedOptionLength != serializeOpts.Length() { |
| ip.SetHeaderLength(uint8(ipHeaderLength)) |
| ip.Options()[paddedOptionLength-1] = 0xff |
| ip.SetHeaderLength(0) |
| } |
| ip.Encode(&header.IPv4Fields{ |
| Options: serializeOpts, |
| }) |
| options := ip.Options() |
| wantOptions := test.encodedOptions |
| if got, want := int(ip.HeaderLength()), test.wantIHL; got != want { |
| t.Errorf("got IHL of %d, want %d", got, want) |
| } |
| |
| // cmp.Diff does not consider nil slices equal to empty slices, but we do. |
| if len(wantOptions) == 0 && len(options) == 0 { |
| return |
| } |
| |
| if diff := cmp.Diff(wantOptions, options); diff != "" { |
| t.Errorf("options mismatch (-want +got):\n%s", diff) |
| } |
| }) |
| } |
| } |