| // Copyright 2016 The Netstack 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 context provides a test context for use in tcp tests. It also |
| // provides helper methods to assert/check certain behaviours. |
| package context |
| |
| import ( |
| "bytes" |
| "testing" |
| "time" |
| |
| "github.com/google/netstack/tcpip" |
| "github.com/google/netstack/tcpip/buffer" |
| "github.com/google/netstack/tcpip/checker" |
| "github.com/google/netstack/tcpip/header" |
| "github.com/google/netstack/tcpip/link/channel" |
| "github.com/google/netstack/tcpip/link/sniffer" |
| "github.com/google/netstack/tcpip/network/ipv4" |
| "github.com/google/netstack/tcpip/network/ipv6" |
| "github.com/google/netstack/tcpip/seqnum" |
| "github.com/google/netstack/tcpip/stack" |
| "github.com/google/netstack/tcpip/transport/tcp" |
| "github.com/google/netstack/waiter" |
| ) |
| |
| const ( |
| // StackAddr is the IPv4 address assigned to the stack. |
| StackAddr = "\x0a\x00\x00\x01" |
| |
| // StackPort is used as the listening port in tests for passive |
| // connects. |
| StackPort = 1234 |
| |
| // TestAddr is the source address for packets sent to the stack via the |
| // link layer endpoint. |
| TestAddr = "\x0a\x00\x00\x02" |
| |
| // TestPort is the TCP port used for packets sent to the stack |
| // via the link layer endpoint. |
| TestPort = 4096 |
| |
| // StackV6Addr is the IPv6 address assigned to the stack. |
| StackV6Addr = "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01" |
| |
| // TestV6Addr is the source address for packets sent to the stack via |
| // the link layer endpoint. |
| TestV6Addr = "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02" |
| |
| // StackV4MappedAddr is StackAddr as a mapped v6 address. |
| StackV4MappedAddr = "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff" + StackAddr |
| |
| // TestV4MappedAddr is TestAddr as a mapped v6 address. |
| TestV4MappedAddr = "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff" + TestAddr |
| |
| // V4MappedWildcardAddr is the mapped v6 representation of 0.0.0.0. |
| V4MappedWildcardAddr = "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\x00\x00\x00\x00" |
| |
| // testInitialSequenceNumber is the initial sequence number sent in packets that |
| // are sent in response to a SYN or in the initial SYN sent to the stack. |
| testInitialSequenceNumber = 789 |
| ) |
| |
| // defaultWindowScale value specified here depends on the tcp.DefaultBufferSize |
| // constant defined in the tcp/endpoint.go because the tcp.DefaultBufferSize is |
| // used in tcp.newHandshake to determine the window scale to use when sending a |
| // SYN/SYN-ACK. |
| var defaultWindowScale = tcp.FindWndScale(tcp.DefaultBufferSize) |
| |
| // Headers is used to represent the TCP header fields when building a |
| // new packet. |
| type Headers struct { |
| // SrcPort holds the src port value to be used in the packet. |
| SrcPort uint16 |
| |
| // DstPort holds the destination port value to be used in the packet. |
| DstPort uint16 |
| |
| // SeqNum is the value of the sequence number field in the TCP header. |
| SeqNum seqnum.Value |
| |
| // AckNum represents the acknowledgement number field in the TCP header. |
| AckNum seqnum.Value |
| |
| // Flags are the TCP flags in the the TCP header. |
| Flags int |
| |
| // RcvWnd is the window to be advertised in the ReceiveWindow field of |
| // the TCP header. |
| RcvWnd seqnum.Size |
| |
| // TCPOpts holds the options to be sent in the option field of the TCP |
| // header. |
| TCPOpts []byte |
| } |
| |
| // Context provides an initialized Network stack and a link layer endpoint |
| // for use in TCP tests. |
| type Context struct { |
| t *testing.T |
| linkEP *channel.Endpoint |
| s *stack.Stack |
| |
| // IRS holds the initial sequence number in the SYN sent by endpoint in |
| // case of an active connect or the sequence number sent by the endpoint |
| // in the SYN-ACK sent in response to a SYN when listening in passive |
| // mode. |
| IRS seqnum.Value |
| |
| // Port holds the port bound by EP below in case of an active connect or |
| // the listening port number in case of a passive connect. |
| Port uint16 |
| |
| // EP is the test endpoint in the stack owned by this context. This endpoint |
| // is used in various tests to either initiate an active connect or is used |
| // as a passive listening endpoint to accept inbound connections. |
| EP tcpip.Endpoint |
| |
| // Wq is the wait queue associated with EP and is used to block for events |
| // on EP. |
| WQ waiter.Queue |
| |
| // TimeStampEnabled is true if ep is connected with the timestamp option |
| // enabled. |
| TimeStampEnabled bool |
| } |
| |
| // New allocates and initializes a test context containing a new |
| // stack and a link-layer endpoint. |
| func New(t *testing.T, mtu uint32) *Context { |
| s := stack.New([]string{ipv4.ProtocolName, ipv6.ProtocolName}, []string{tcp.ProtocolName}) |
| |
| id, linkEP := channel.New(256, mtu, "") |
| if testing.Verbose() { |
| id = sniffer.New(id) |
| } |
| if err := s.CreateNIC(1, id); err != nil { |
| t.Fatalf("CreateNIC failed: %v", err) |
| } |
| |
| if err := s.AddAddress(1, ipv4.ProtocolNumber, StackAddr); err != nil { |
| t.Fatalf("AddAddress failed: %v", err) |
| } |
| |
| if err := s.AddAddress(1, ipv6.ProtocolNumber, StackV6Addr); err != nil { |
| t.Fatalf("AddAddress failed: %v", err) |
| } |
| |
| s.SetRouteTable([]tcpip.Route{ |
| { |
| Destination: "\x00\x00\x00\x00", |
| Mask: "\x00\x00\x00\x00", |
| Gateway: "", |
| NIC: 1, |
| }, |
| { |
| Destination: "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", |
| Mask: "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", |
| Gateway: "", |
| NIC: 1, |
| }, |
| }) |
| |
| return &Context{ |
| t: t, |
| s: s, |
| linkEP: linkEP, |
| } |
| } |
| |
| // Cleanup closes the context endpoint if required. |
| func (c *Context) Cleanup() { |
| if c.EP != nil { |
| c.EP.Close() |
| } |
| } |
| |
| // Stack returns a reference to the stack in the Context. |
| func (c *Context) Stack() *stack.Stack { |
| return c.s |
| } |
| |
| // CheckNoPacketTimeout verifies that no packet is received during the time |
| // specified by wait. |
| func (c *Context) CheckNoPacketTimeout(errMsg string, wait time.Duration) { |
| select { |
| case <-c.linkEP.C: |
| c.t.Fatalf(errMsg) |
| |
| case <-time.After(wait): |
| } |
| } |
| |
| // CheckNoPacket verifies that no packet is received for 1 second. |
| func (c *Context) CheckNoPacket(errMsg string) { |
| c.CheckNoPacketTimeout(errMsg, 1*time.Second) |
| } |
| |
| // GetPacket reads a packet from the link layer endpoint and verifies |
| // that it is an IPv4 packet with the expected source and destination |
| // addresses. It will fail with an error if no packet is received for |
| // 2 seconds. |
| func (c *Context) GetPacket() []byte { |
| select { |
| case p := <-c.linkEP.C: |
| if p.Proto != ipv4.ProtocolNumber { |
| c.t.Fatalf("Bad network protocol: got %v, wanted %v", p.Proto, ipv4.ProtocolNumber) |
| } |
| b := make([]byte, len(p.Header)+len(p.Payload)) |
| copy(b, p.Header) |
| copy(b[len(p.Header):], p.Payload) |
| |
| checker.IPv4(c.t, b, checker.SrcAddr(StackAddr), checker.DstAddr(TestAddr)) |
| return b |
| |
| case <-time.After(2 * time.Second): |
| c.t.Fatalf("Packet wasn't written out") |
| } |
| |
| return nil |
| } |
| |
| // SendPacket builds and sends a TCP segment(with the provided payload & TCP |
| // headers) in an IPv4 packet via the link layer endpoint. |
| func (c *Context) SendPacket(payload []byte, h *Headers) { |
| // Allocate a buffer for data and headers. |
| buf := buffer.NewView(header.TCPMinimumSize + header.IPv4MinimumSize + len(h.TCPOpts) + len(payload)) |
| copy(buf[len(buf)-len(payload):], payload) |
| copy(buf[len(buf)-len(payload)-len(h.TCPOpts):], h.TCPOpts) |
| |
| // Initialize the IP header. |
| ip := header.IPv4(buf) |
| ip.Encode(&header.IPv4Fields{ |
| IHL: header.IPv4MinimumSize, |
| TotalLength: uint16(len(buf)), |
| TTL: 65, |
| Protocol: uint8(tcp.ProtocolNumber), |
| SrcAddr: TestAddr, |
| DstAddr: StackAddr, |
| }) |
| ip.SetChecksum(^ip.CalculateChecksum()) |
| |
| // Initialize the TCP header. |
| t := header.TCP(buf[header.IPv4MinimumSize:]) |
| t.Encode(&header.TCPFields{ |
| SrcPort: h.SrcPort, |
| DstPort: h.DstPort, |
| SeqNum: uint32(h.SeqNum), |
| AckNum: uint32(h.AckNum), |
| DataOffset: uint8(header.TCPMinimumSize + len(h.TCPOpts)), |
| Flags: uint8(h.Flags), |
| WindowSize: uint16(h.RcvWnd), |
| }) |
| |
| // Calculate the TCP pseudo-header checksum. |
| xsum := header.Checksum([]byte(TestAddr), 0) |
| xsum = header.Checksum([]byte(StackAddr), xsum) |
| xsum = header.Checksum([]byte{0, uint8(tcp.ProtocolNumber)}, xsum) |
| |
| // Calculate the TCP checksum and set it. |
| length := uint16(header.TCPMinimumSize + len(h.TCPOpts) + len(payload)) |
| xsum = header.Checksum(payload, xsum) |
| t.SetChecksum(^t.CalculateChecksum(xsum, length)) |
| |
| // Inject packet. |
| var views [1]buffer.View |
| vv := buf.ToVectorisedView(views) |
| c.linkEP.Inject(ipv4.ProtocolNumber, &vv) |
| } |
| |
| // SendAck sends an ACK packet. |
| func (c *Context) SendAck(seq seqnum.Value, bytesReceived int) { |
| c.SendPacket(nil, &Headers{ |
| SrcPort: TestPort, |
| DstPort: c.Port, |
| Flags: header.TCPFlagAck, |
| SeqNum: seqnum.Value(testInitialSequenceNumber).Add(1), |
| AckNum: c.IRS.Add(1 + seqnum.Size(bytesReceived)), |
| RcvWnd: 30000, |
| }) |
| } |
| |
| // ReceiveAndCheckPacket reads a packet from the link layer endpoint and |
| // verifies that the packet packet payload of packet matches the slice |
| // of data indicated by offset & size. |
| func (c *Context) ReceiveAndCheckPacket(data []byte, offset, size int) { |
| b := c.GetPacket() |
| checker.IPv4(c.t, b, |
| checker.PayloadLen(size+header.TCPMinimumSize), |
| checker.TCP( |
| checker.DstPort(TestPort), |
| checker.SeqNum(uint32(c.IRS.Add(seqnum.Size(1+offset)))), |
| checker.AckNum(uint32(seqnum.Value(testInitialSequenceNumber).Add(1))), |
| checker.TCPFlagsMatch(header.TCPFlagAck, ^uint8(header.TCPFlagPsh)), |
| ), |
| ) |
| |
| pdata := data[offset:][:size] |
| if p := b[header.IPv4MinimumSize+header.TCPMinimumSize:]; bytes.Compare(pdata, p) != 0 { |
| c.t.Fatalf("Data is different: expected %v, got %v", pdata, p) |
| } |
| } |
| |
| // CreateV6Endpoint creates and initializes c.ep as a IPv6 Endpoint. If v6Only |
| // is true then it sets the IP_V6ONLY option on the socket to make it a IPv6 |
| // only endpoint instead of a default dual stack socket. |
| func (c *Context) CreateV6Endpoint(v6only bool) { |
| var err *tcpip.Error |
| c.EP, err = c.s.NewEndpoint(tcp.ProtocolNumber, ipv6.ProtocolNumber, &c.WQ) |
| if err != nil { |
| c.t.Fatalf("NewEndpoint failed: %v", err) |
| } |
| |
| var v tcpip.V6OnlyOption |
| if v6only { |
| v = 1 |
| } |
| if err := c.EP.SetSockOpt(v); err != nil { |
| c.t.Fatalf("SetSockOpt failed failed: %v", err) |
| } |
| } |
| |
| // GetV6Packet reads a single packet from the link layer endpoint of the context |
| // and asserts that it is an IPv6 Packet with the expected src/dest addresses. |
| func (c *Context) GetV6Packet() []byte { |
| select { |
| case p := <-c.linkEP.C: |
| if p.Proto != ipv6.ProtocolNumber { |
| c.t.Fatalf("Bad network protocol: got %v, wanted %v", p.Proto, ipv6.ProtocolNumber) |
| } |
| b := make([]byte, len(p.Header)+len(p.Payload)) |
| copy(b, p.Header) |
| copy(b[len(p.Header):], p.Payload) |
| |
| checker.IPv6(c.t, b, checker.SrcAddr(StackV6Addr), checker.DstAddr(TestV6Addr)) |
| return b |
| |
| case <-time.After(2 * time.Second): |
| c.t.Fatalf("Packet wasn't written out") |
| } |
| |
| return nil |
| } |
| |
| // SendV6Packet builds and sends an IPv6 Packet via the link layer endpoint of |
| // the context. |
| func (c *Context) SendV6Packet(payload []byte, h *Headers) { |
| // Allocate a buffer for data and headers. |
| buf := buffer.NewView(header.TCPMinimumSize + header.IPv6MinimumSize + len(payload)) |
| copy(buf[len(buf)-len(payload):], payload) |
| |
| // Initialize the IP header. |
| ip := header.IPv6(buf) |
| ip.Encode(&header.IPv6Fields{ |
| PayloadLength: uint16(header.TCPMinimumSize + len(payload)), |
| NextHeader: uint8(tcp.ProtocolNumber), |
| HopLimit: 65, |
| SrcAddr: TestV6Addr, |
| DstAddr: StackV6Addr, |
| }) |
| |
| // Initialize the TCP header. |
| t := header.TCP(buf[header.IPv6MinimumSize:]) |
| t.Encode(&header.TCPFields{ |
| SrcPort: h.SrcPort, |
| DstPort: h.DstPort, |
| SeqNum: uint32(h.SeqNum), |
| AckNum: uint32(h.AckNum), |
| DataOffset: header.TCPMinimumSize, |
| Flags: uint8(h.Flags), |
| WindowSize: uint16(h.RcvWnd), |
| }) |
| |
| // Calculate the TCP pseudo-header checksum. |
| xsum := header.Checksum([]byte(TestV6Addr), 0) |
| xsum = header.Checksum([]byte(StackV6Addr), xsum) |
| xsum = header.Checksum([]byte{0, uint8(tcp.ProtocolNumber)}, xsum) |
| |
| // Calculate the TCP checksum and set it. |
| length := uint16(header.TCPMinimumSize + len(payload)) |
| xsum = header.Checksum(payload, xsum) |
| t.SetChecksum(^t.CalculateChecksum(xsum, length)) |
| |
| // Inject packet. |
| var views [1]buffer.View |
| vv := buf.ToVectorisedView(views) |
| c.linkEP.Inject(ipv6.ProtocolNumber, &vv) |
| } |
| |
| // CreateConnected creates a connected TCP endpoint. |
| func (c *Context) CreateConnected(iss seqnum.Value, rcvWnd seqnum.Size, epRcvBuf *tcpip.ReceiveBufferSizeOption) { |
| c.CreateConnectedWithRawOptions(iss, rcvWnd, epRcvBuf, nil) |
| } |
| |
| // CreateConnectedWithRawOptions creates a connected TCP endpoint and sends |
| // the specified option bytes as the Option field in the initial SYN packet. |
| // |
| // It also sets the receive buffer for the endpoint to the specified |
| // value in epRcvBuf. |
| func (c *Context) CreateConnectedWithRawOptions(iss seqnum.Value, rcvWnd seqnum.Size, epRcvBuf *tcpip.ReceiveBufferSizeOption, options []byte) { |
| // Create TCP endpoint. |
| var err *tcpip.Error |
| c.EP, err = c.s.NewEndpoint(tcp.ProtocolNumber, ipv4.ProtocolNumber, &c.WQ) |
| if err != nil { |
| c.t.Fatalf("NewEndpoint failed: %v", err) |
| } |
| |
| if epRcvBuf != nil { |
| if err := c.EP.SetSockOpt(*epRcvBuf); err != nil { |
| c.t.Fatalf("SetSockOpt failed failed: %v", err) |
| } |
| } |
| |
| // Start connection attempt. |
| waitEntry, notifyCh := waiter.NewChannelEntry(nil) |
| c.WQ.EventRegister(&waitEntry, waiter.EventOut) |
| defer c.WQ.EventUnregister(&waitEntry) |
| |
| err = c.EP.Connect(tcpip.FullAddress{Addr: TestAddr, Port: TestPort}) |
| if err != tcpip.ErrConnectStarted { |
| c.t.Fatalf("Unexpected return value from Connect: %v", err) |
| } |
| |
| // Receive SYN packet. |
| b := c.GetPacket() |
| checker.IPv4(c.t, b, |
| checker.TCP( |
| checker.DstPort(TestPort), |
| checker.TCPFlags(header.TCPFlagSyn), |
| ), |
| ) |
| |
| tcp := header.TCP(header.IPv4(b).Payload()) |
| c.IRS = seqnum.Value(tcp.SequenceNumber()) |
| |
| c.SendPacket(nil, &Headers{ |
| SrcPort: tcp.DestinationPort(), |
| DstPort: tcp.SourcePort(), |
| Flags: header.TCPFlagSyn | header.TCPFlagAck, |
| SeqNum: iss, |
| AckNum: c.IRS.Add(1), |
| RcvWnd: rcvWnd, |
| TCPOpts: options, |
| }) |
| |
| // Receive ACK packet. |
| checker.IPv4(c.t, c.GetPacket(), |
| checker.TCP( |
| checker.DstPort(TestPort), |
| checker.TCPFlags(header.TCPFlagAck), |
| checker.SeqNum(uint32(c.IRS)+1), |
| checker.AckNum(uint32(iss)+1), |
| ), |
| ) |
| |
| // Wait for connection to be established. |
| select { |
| case <-notifyCh: |
| err = c.EP.GetSockOpt(tcpip.ErrorOption{}) |
| if err != nil { |
| c.t.Fatalf("Unexpected error when connecting: %v", err) |
| } |
| case <-time.After(1 * time.Second): |
| c.t.Fatalf("Timed out waiting for connection") |
| } |
| |
| c.Port = tcp.SourcePort() |
| } |
| |
| // RawEndpoint is just a small wrapper around a TCP endpoint's state to make |
| // sending data and ACK packets easy while being able to manipulate the sequence |
| // numbers and timestamp values as needed. |
| type RawEndpoint struct { |
| C *Context |
| SrcPort uint16 |
| DstPort uint16 |
| Flags int |
| NextSeqNum seqnum.Value |
| AckNum seqnum.Value |
| WndSize seqnum.Size |
| RecentTS uint32 // Stores the latest timestamp to echo back. |
| TSVal uint32 // TSVal stores the last timestamp sent by this endpoint. |
| } |
| |
| // SendPacketWithTS embeds the provided tsVal in the Timestamp option |
| // for the packet to be sent out. |
| func (r *RawEndpoint) SendPacketWithTS(payload []byte, tsVal uint32) { |
| r.TSVal = tsVal |
| // Increment TSVal by 1 from the value sent in the SYN and echo the |
| // TSVal in the SYN-ACK in the TSEcr field. |
| tsOpt := header.EncodeTSOption(r.TSVal, r.RecentTS) |
| r.SendPacket(payload, tsOpt[:]) |
| } |
| |
| // SendPacket is a small wrapper function to build and send packets. |
| func (r *RawEndpoint) SendPacket(payload []byte, opts []byte) { |
| packetHeaders := &Headers{ |
| SrcPort: r.SrcPort, |
| DstPort: r.DstPort, |
| Flags: r.Flags, |
| SeqNum: r.NextSeqNum, |
| AckNum: r.AckNum, |
| RcvWnd: r.WndSize, |
| TCPOpts: opts, |
| } |
| r.C.SendPacket(payload, packetHeaders) |
| r.NextSeqNum = r.NextSeqNum.Add(seqnum.Size(len(payload))) |
| } |
| |
| // VerifyACKWithTS verifies that the tsEcr field in the ack matches the provided |
| // tsVal. |
| func (r *RawEndpoint) VerifyACKWithTS(tsVal uint32) { |
| // Read ACK and verify that tsEcr of ACK packet is [1,2,3,4] |
| ackPacket := r.C.GetPacket() |
| checker.IPv4(r.C.t, ackPacket, |
| checker.TCP( |
| checker.DstPort(r.SrcPort), |
| checker.TCPFlags(header.TCPFlagAck), |
| checker.SeqNum(uint32(r.AckNum)), |
| checker.AckNum(uint32(r.NextSeqNum)), |
| checker.TCPTimestampChecker(true, 0, tsVal), |
| ), |
| ) |
| // Store the parsed TSVal from the ack as recentTS. |
| tcpSeg := header.TCP(header.IPv4(ackPacket).Payload()) |
| opts := tcpSeg.ParsedOptions() |
| r.RecentTS = opts.TSVal |
| } |
| |
| // CreateConnectedWithOptions creates and connects c.ep with the specified TCP |
| // options enabled and returns a RawEndpoint which represents the other end of |
| // the connection. |
| // |
| // It also verifies where required(eg.Timestamp) that the ACK to the SYN-ACK |
| // does not carry an option that was not requested. |
| func (c *Context) CreateConnectedWithOptions(wantOptions header.TCPSynOptions) *RawEndpoint { |
| var err *tcpip.Error |
| c.EP, err = c.s.NewEndpoint(tcp.ProtocolNumber, ipv4.ProtocolNumber, &c.WQ) |
| if err != nil { |
| c.t.Fatalf("c.s.NewEndpoint(tcp, ipv4...) = %v", err) |
| } |
| |
| // Start connection attempt. |
| waitEntry, notifyCh := waiter.NewChannelEntry(nil) |
| c.WQ.EventRegister(&waitEntry, waiter.EventOut) |
| defer c.WQ.EventUnregister(&waitEntry) |
| |
| testFullAddr := tcpip.FullAddress{Addr: TestAddr, Port: TestPort} |
| err = c.EP.Connect(testFullAddr) |
| if err != tcpip.ErrConnectStarted { |
| c.t.Fatalf("c.ep.Connect(%v) = %v", testFullAddr, err) |
| } |
| // Receive SYN packet. |
| b := c.GetPacket() |
| // Validate that the syn has the timestamp option and a valid |
| // TS value. |
| checker.IPv4(c.t, b, |
| checker.TCP( |
| checker.DstPort(TestPort), |
| checker.TCPFlags(header.TCPFlagSyn), |
| checker.TCPSynOptions(header.TCPSynOptions{ |
| MSS: uint16(c.linkEP.MTU() - header.IPv4MinimumSize - header.TCPMinimumSize), |
| TS: true, |
| WS: defaultWindowScale, |
| }), |
| ), |
| ) |
| tcpSeg := header.TCP(header.IPv4(b).Payload()) |
| synOptions := header.ParseSynOptions(tcpSeg.Options(), false) |
| |
| // Build options w/ tsVal to be sent in the SYN-ACK. |
| var synAckOptions []byte |
| if wantOptions.TS { |
| tsOpt := header.EncodeTSOption(wantOptions.TSVal, synOptions.TSVal) |
| synAckOptions = append(synAckOptions, tsOpt[:]...) |
| } |
| |
| // Build SYN-ACK. |
| c.IRS = seqnum.Value(tcpSeg.SequenceNumber()) |
| iss := seqnum.Value(testInitialSequenceNumber) |
| c.SendPacket(nil, &Headers{ |
| SrcPort: tcpSeg.DestinationPort(), |
| DstPort: tcpSeg.SourcePort(), |
| Flags: header.TCPFlagSyn | header.TCPFlagAck, |
| SeqNum: iss, |
| AckNum: c.IRS.Add(1), |
| RcvWnd: 30000, |
| TCPOpts: synAckOptions[:], |
| }) |
| |
| // Read ACK. |
| ackPacket := c.GetPacket() |
| |
| // Verify TCP header fields. |
| tcpCheckers := []checker.TransportChecker{ |
| checker.DstPort(TestPort), |
| checker.TCPFlags(header.TCPFlagAck), |
| checker.SeqNum(uint32(c.IRS) + 1), |
| checker.AckNum(uint32(iss) + 1), |
| } |
| |
| // Verify that tsEcr of ACK packet is wantOptions.TSVal if the |
| // timestamp option was enabled, if not then we verify that |
| // there is no timestamp in the ACK packet. |
| if wantOptions.TS { |
| tcpCheckers = append(tcpCheckers, checker.TCPTimestampChecker(true, 0, wantOptions.TSVal)) |
| } else { |
| tcpCheckers = append(tcpCheckers, checker.TCPTimestampChecker(false, 0, 0)) |
| } |
| |
| checker.IPv4(c.t, ackPacket, checker.TCP(tcpCheckers...)) |
| |
| ackSeg := header.TCP(header.IPv4(ackPacket).Payload()) |
| ackOptions := ackSeg.ParsedOptions() |
| |
| // Wait for connection to be established. |
| select { |
| case <-notifyCh: |
| err = c.EP.GetSockOpt(tcpip.ErrorOption{}) |
| if err != nil { |
| c.t.Fatalf("Unexpected error when connecting: %v", err) |
| } |
| case <-time.After(1 * time.Second): |
| c.t.Fatalf("Timed out waiting for connection") |
| } |
| |
| // Store the source port in use by the endpoint. |
| c.Port = tcpSeg.SourcePort() |
| |
| // Mark in context that timestamp option is enabled for this endpoint. |
| c.TimeStampEnabled = true |
| |
| return &RawEndpoint{ |
| C: c, |
| SrcPort: tcpSeg.DestinationPort(), |
| DstPort: tcpSeg.SourcePort(), |
| Flags: header.TCPFlagAck | header.TCPFlagPsh, |
| NextSeqNum: iss + 1, |
| AckNum: c.IRS.Add(1), |
| WndSize: 30000, |
| RecentTS: ackOptions.TSVal, |
| TSVal: wantOptions.TSVal, |
| } |
| } |
| |
| // AcceptWithOptions initializes a listening endpoint and connects to it with the |
| // provided options enabled. It also verifies that the SYN-ACK has the expected |
| // values for the provided options. |
| func (c *Context) AcceptWithOptions(wndScale int, synOptions header.TCPSynOptions) { |
| // Create EP and start listening. |
| wq := &waiter.Queue{} |
| ep, err := c.s.NewEndpoint(tcp.ProtocolNumber, ipv4.ProtocolNumber, wq) |
| if err != nil { |
| c.t.Fatalf("NewEndpoint failed: %v", err) |
| } |
| defer ep.Close() |
| |
| if err := ep.Bind(tcpip.FullAddress{Port: StackPort}, nil); err != nil { |
| c.t.Fatalf("Bind failed: %v", err) |
| } |
| |
| if err := ep.Listen(10); err != nil { |
| c.t.Fatalf("Listen failed: %v", err) |
| } |
| |
| c.PassiveConnectWithOptions(100, wndScale, synOptions) |
| |
| // Try to accept the connection. |
| we, ch := waiter.NewChannelEntry(nil) |
| wq.EventRegister(&we, waiter.EventIn) |
| defer wq.EventUnregister(&we) |
| |
| c.EP, _, err = ep.Accept() |
| if err == tcpip.ErrWouldBlock { |
| // Wait for connection to be established. |
| select { |
| case <-ch: |
| c.EP, _, err = ep.Accept() |
| if err != nil { |
| c.t.Fatalf("Accept failed: %v", err) |
| } |
| |
| case <-time.After(1 * time.Second): |
| c.t.Fatalf("Timed out waiting for accept") |
| } |
| } |
| } |
| |
| // PassiveConnect just disables WindowScaling and delegates the call to |
| // PassiveConnectWithOptions. |
| func (c *Context) PassiveConnect(maxPayload, wndScale int, synOptions header.TCPSynOptions) { |
| synOptions.WS = -1 |
| c.PassiveConnectWithOptions(maxPayload, wndScale, synOptions) |
| } |
| |
| // PassiveConnectWithOptions initiates a new connection (with the specified TCP |
| // options enabled) to the port on which the Context.ep is listening for new |
| // connections. It also validates that the SYN-ACK has the expected values for |
| // the enabled options. |
| // |
| // NOTE: MSS is not a negotiated option and it can be asymmetric |
| // in each direction. This function uses the maxPayload to set the MSS to be |
| // sent to the peer on a connect and validates that the MSS in the SYN-ACK |
| // response is equal to the MTU - (tcphdr len + iphdr len). |
| // |
| // wndScale is the expected window scale in the SYN-ACK and synOptions.WS is the |
| // value of the window scaling option to be sent in the SYN. If synOptions.WS > |
| // 0 then we send the WindowScale option. |
| func (c *Context) PassiveConnectWithOptions(maxPayload, wndScale int, synOptions header.TCPSynOptions) { |
| opts := []byte{ |
| header.TCPOptionMSS, 4, byte(maxPayload / 256), byte(maxPayload % 256), |
| } |
| |
| if synOptions.WS >= 0 { |
| opts = append(opts, []byte{ |
| header.TCPOptionWS, 3, byte(synOptions.WS), header.TCPOptionNOP, |
| }...) |
| } |
| if synOptions.TS { |
| tsOpt := header.EncodeTSOption(synOptions.TSVal, synOptions.TSEcr) |
| opts = append(opts, tsOpt[:]...) |
| } |
| |
| // Send a SYN request. |
| iss := seqnum.Value(testInitialSequenceNumber) |
| c.SendPacket(nil, &Headers{ |
| SrcPort: TestPort, |
| DstPort: StackPort, |
| Flags: header.TCPFlagSyn, |
| SeqNum: iss, |
| RcvWnd: 30000, |
| TCPOpts: opts, |
| }) |
| |
| // Receive the SYN-ACK reply. Make sure MSS is present. |
| b := c.GetPacket() |
| tcp := header.TCP(header.IPv4(b).Payload()) |
| c.IRS = seqnum.Value(tcp.SequenceNumber()) |
| |
| tcpCheckers := []checker.TransportChecker{ |
| checker.SrcPort(StackPort), |
| checker.DstPort(TestPort), |
| checker.TCPFlags(header.TCPFlagAck | header.TCPFlagSyn), |
| checker.AckNum(uint32(iss) + 1), |
| checker.TCPSynOptions(header.TCPSynOptions{MSS: synOptions.MSS, WS: wndScale}), |
| } |
| |
| // If TS option was enabled in the original SYN then add a checker to |
| // validate the Timestamp option in the SYN-ACK. |
| if synOptions.TS { |
| tcpCheckers = append(tcpCheckers, checker.TCPTimestampChecker(synOptions.TS, 0, synOptions.TSVal)) |
| } else { |
| tcpCheckers = append(tcpCheckers, checker.TCPTimestampChecker(false, 0, 0)) |
| } |
| |
| checker.IPv4(c.t, b, checker.TCP(tcpCheckers...)) |
| rcvWnd := seqnum.Size(30000) |
| ackHeaders := &Headers{ |
| SrcPort: TestPort, |
| DstPort: StackPort, |
| Flags: header.TCPFlagAck, |
| SeqNum: iss + 1, |
| AckNum: c.IRS + 1, |
| RcvWnd: rcvWnd, |
| } |
| |
| // If WS was expected to be in effect then scale the advertised window |
| // correspondingly. |
| if synOptions.WS > 0 { |
| ackHeaders.RcvWnd = rcvWnd >> byte(synOptions.WS) |
| } |
| |
| if synOptions.TS { |
| // Echo the tsVal back to the peer in the tsEcr field of the |
| // timestamp option. |
| opts := tcp.ParsedOptions() |
| // Increment TSVal by 1 from the value sent in the SYN and echo |
| // the TSVal in the SYN-ACK in the TSEcr field. |
| tsOpt := header.EncodeTSOption(synOptions.TSVal+1, opts.TSVal) |
| ackHeaders.TCPOpts = tsOpt[:] |
| } |
| |
| // Send ACK. |
| c.SendPacket(nil, ackHeaders) |
| |
| c.Port = StackPort |
| } |