blob: a662f4857161e85332acb0385a0146895fc15dbf [file] [log] [blame]
// Copyright 2020 The Chromium OS 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 network provides general CrOS network goodies.
package network
import (
"chromiumos/tast/errors"
)
// Response types.
type response int
const (
// Drops the packet and acts like it never happened.
noAction response = 0
// Signals that the handler wishes to send a packet.
haveResponse response = 1 << 0
// Signals that the handler wishes to be removed from the handling queue.
// The handler will be asked to generate a packet first if the handler
// signaled that it wished to do so with responseHaveResponse.
popHandler response = 1 << 1
// Signals that the handler wants to end the test on a failure.
testFailed response = 1 << 2
// Signals that the handler wants to end the test because it succeeded.
// Note that the failure bit has precedence over the success bit.
testSucceeded response = 1 << 3
)
// DHCP handling rule types.
type rule int
const (
// respondToDiscovery accepts a DISCOVER packet and responds with an OFFER.
respondToDiscovery rule = iota
// rejectRequest receives a REQUEST and responds with a NAK.
rejectRequest
// respondToRequest receives a REQUEST and responds with an ACKNOWLEDGEMENT.
respondToRequest
// respondToPostT2Request is similar to respondToRequest except that it
// expects request packets like those sent after the T2 deadline.
respondToPostT2Request
// acceptRelease accepts any RELEASE packet.
acceptRelease
// rejectAndRespondToRequest accepts a REQUEST packet and responds with both
// an ACKNOWLEDGEMENT and a NAK.
rejectAndRespondToRequest
// acceptDecline accepts any DECLINE packet.
acceptDecline
)
// dhcpHandlingRule records expectations for a DhcpTestServer.
// When a handling rule reaches the front of the dhcpTestServer handling rule
// queue, the server begins to ask the rule what it should do with each incoming
// DHCP packet (in the form of a dhcpPacket). The handle() method is expected to
// return a response that indicates whether the packet should be ignored or
// responded to and whether the test failed, succeeded, or is continuing, and an
// action that refers to whether or not the rule should be be removed from the
// test server's handling rule queue.
type dhcpHandlingRule struct {
// ruleType describes the handling rule type.
ruleType rule
// isFinalHandler is true when this rule is the last handler.
isFinalHandler bool
// options maps the packet options to their values.
options optionMap
// fields maps the packet fields to their values.
fields fieldMap
// forceReplyOptions are options that will be injected into the response.
forceReplyOptions []option
// msgType is the expected message type
msgType msgType
// respPktCnt is the number of packets this handler will respond to.
respPktCnt int
// intendedIP is the IP to be offered.
intendedIP string
// svrIP is the server offering the intendedIP.
svrIP string
// shouldRespond is true if the server should respond to the DHCP packet.
shouldRespond bool
// expReqIP is the expected requested IP.
expReqIP string
// expSvrIP is the expected server IP.
expSvrIP string
// grantedIP is the IP to be granted.
grantedIP string
// expSvrIPSet is true if the serverID option is expected to be set.
expSvrIPSet bool
// nakFirst is true when the rejectAndRespondToRequest rule should send a NAK
// before an ACK.
nakFirst bool
// respCounter is the number of responses this rule has handled.
respCounter int
}
// handle is called by the test server to ask a handling rule whether it wants
// to take some action in response to a packet. The handler should return some
// combination of response* bits as described above.
func (d *dhcpHandlingRule) handle(queryPacket *dhcpPacket) response {
if !d.isOurMsgType(queryPacket) {
return noAction
}
if d.ruleType == respondToRequest ||
d.ruleType == respondToPostT2Request ||
d.ruleType == acceptRelease ||
d.ruleType == rejectAndRespondToRequest ||
d.ruleType == acceptDecline {
svrIP := queryPacket.option(serverID)
if (svrIP == nil) == d.expSvrIPSet || (d.expSvrIPSet && svrIP != d.expSvrIP) {
return noAction
}
}
if d.ruleType == respondToRequest ||
d.ruleType == respondToPostT2Request ||
d.ruleType == rejectAndRespondToRequest {
if queryPacket.option(requestedIP) != d.expReqIP {
return noAction
}
}
ret := popHandler
if d.isFinalHandler {
ret |= testSucceeded
}
if (d.ruleType == respondToDiscovery ||
d.ruleType == rejectRequest ||
d.ruleType == respondToRequest ||
d.ruleType == respondToPostT2Request ||
d.ruleType == rejectAndRespondToRequest) &&
d.shouldRespond {
ret |= haveResponse
}
return ret
}
// respond is called by the test server to generate a packet to send back to the
// client. This method is called if and only if the response returned from
// handle() had haveResponse set.
func (d *dhcpHandlingRule) respond(queryPacket *dhcpPacket) (*dhcpPacket, error) {
if d.ruleType == acceptRelease || d.ruleType == acceptDecline {
return nil, errors.Errorf("no response for packet type: %d", d.ruleType)
}
if !d.isOurMsgType(queryPacket) {
return nil, errors.New("wrong message type")
}
// If this is a rejectAndRespondToRequest rule, we send a NAK if this is the
// first response and |d.nakFirst| is true, or if this is the second
// response and |d.nakFirst| is false.
shouldSendNAK := d.ruleType == rejectAndRespondToRequest && ((d.respCounter == 0 && d.nakFirst) || (d.respCounter != 0 && !d.nakFirst))
var txnID uint32
var clientHWAddr []byte
var err error
if d.ruleType == respondToDiscovery ||
d.ruleType == rejectRequest ||
d.ruleType == respondToRequest ||
d.ruleType == respondToPostT2Request ||
d.ruleType == rejectAndRespondToRequest {
txnID, err = queryPacket.txnID()
if err != nil {
return nil, err
}
clientHWAddr, err = queryPacket.clientHWAddr()
if err != nil {
return nil, err
}
}
var responsePacket *dhcpPacket
if d.ruleType == respondToDiscovery {
responsePacket, err = createOffer(txnID, clientHWAddr, d.intendedIP, d.svrIP)
} else if d.ruleType == rejectRequest || shouldSendNAK {
responsePacket, err = createNAK(txnID, clientHWAddr)
} else {
responsePacket, err = createAck(txnID, clientHWAddr, d.grantedIP, d.svrIP)
}
if err != nil {
return nil, err
}
if d.ruleType == respondToDiscovery ||
d.ruleType == respondToRequest ||
d.ruleType == respondToPostT2Request ||
(d.ruleType == rejectAndRespondToRequest && !shouldSendNAK) {
requestedParametersInterface := queryPacket.option(parameterRequestList)
if requestedParametersInterface != nil {
requestedParameters, ok := requestedParametersInterface.([]uint8)
if ok {
d.injectOptions(responsePacket, requestedParameters)
}
}
d.injectFields(responsePacket)
}
if d.ruleType == rejectAndRespondToRequest {
d.respCounter++
}
return responsePacket, nil
}
// injectOptions adds options listed in the intersection of
// requestedParameters and |d.Options| to packet. Also include the options
// in the intersection of |d.ForceReplyOptions| and |d.Options|.
func (d *dhcpHandlingRule) injectOptions(packet *dhcpPacket, requestedParameters []uint8) {
for option, value := range d.options {
shouldSet := false
for _, param := range requestedParameters {
if option.number() == param {
shouldSet = true
break
}
}
if !shouldSet {
for _, replyOption := range d.forceReplyOptions {
if option == replyOption {
shouldSet = true
break
}
}
}
if shouldSet {
packet.setOption(option, value)
}
}
}
// injectFields adds fields listed in |d.fields| to packet.
func (d *dhcpHandlingRule) injectFields(packet *dhcpPacket) {
for field, value := range d.fields {
packet.setField(field, value)
}
}
// isOurMsgType checks if the packet's message type matches the message type
// handled by this rule.
func (d *dhcpHandlingRule) isOurMsgType(packet *dhcpPacket) bool {
msgType, err := packet.msgType()
return err == nil && msgType == d.msgType
}
// newRespondToDiscovery creates a handler that accepts any DISCOVER packet
// received by the server. In response to such a packet, the handler will
// construct an OFFER packet offering intendedIP from a server at svrIP.
func newRespondToDiscovery(intendedIP, svrIP string, options optionMap, fields fieldMap, shouldRespond bool) *dhcpHandlingRule {
return &dhcpHandlingRule{
ruleType: respondToDiscovery,
options: options,
fields: fields,
msgType: discovery,
respPktCnt: 1,
intendedIP: intendedIP,
svrIP: svrIP,
shouldRespond: shouldRespond,
}
}
// newRejectRequestRule creates a handler that receives a REQUEST packet and
// responds with a NAK.
func newRejectRequestRule() *dhcpHandlingRule {
return &dhcpHandlingRule{
ruleType: rejectRequest,
msgType: request,
shouldRespond: true,
}
}
// newRespondToRequest creates a handler that accepts any REQUEST packet that
// contains options for serverID and requestedIP that match expSvrIP and
// |expectedRequestIP| respectively. It responds with an ACKNOWLEDGEMENT packet
// from a DHCP server at responsesvrIP granting responseGrantedIP to a
// client at the address given in the REQUEST packet. If responsesvrIP or
// responseGrantedIP are not given, then they default to expSvrIP and
// expReqIP respectively.
func newRespondToRequest(expReqIP, expSvrIP string, options optionMap, fields fieldMap, shouldRespond bool, responsesvrIP, responseGrantedIP string, expSvrIPSet bool) *dhcpHandlingRule {
rule := dhcpHandlingRule{
ruleType: respondToRequest,
options: options,
fields: fields,
msgType: request,
respPktCnt: 1,
expReqIP: expReqIP,
expSvrIP: expSvrIP,
shouldRespond: shouldRespond,
grantedIP: responseGrantedIP,
svrIP: responsesvrIP,
expSvrIPSet: expSvrIPSet,
}
if len(rule.grantedIP) == 0 {
rule.grantedIP = rule.expReqIP
}
if len(rule.svrIP) == 0 {
rule.svrIP = rule.expSvrIP
}
return &rule
}
// newRespondToPostT2Request creates a handler similar to respondToRequest
// except that it expects request packets like those sent after the T2 deadline
// (see RFC 2131). This is the only time that you can find a request packet
// without the serverID option. It reseponds to packets in exactly the same way.
func newRespondToPostT2Request(expReqIP, responseSvrIP string, options optionMap, fields fieldMap, shouldRespond bool, responseGrantedIP string) *dhcpHandlingRule {
rule := newRespondToRequest(expReqIP, "", options, fields, shouldRespond, responseSvrIP, responseGrantedIP, false)
rule.ruleType = respondToPostT2Request
return rule
}
// newAcceptRelease creates a handler that accepts any RELEASE packet that
// contains an option for serverID that matches expSvrIP. There is no
// response to this packet.
func newAcceptRelease(expSvrIP string, options optionMap, fields fieldMap) *dhcpHandlingRule {
return &dhcpHandlingRule{
ruleType: acceptRelease,
options: options,
fields: fields,
msgType: release,
respPktCnt: 1,
expSvrIP: expSvrIP,
}
}
// newRejectAndRespondToRequest creates a handler that accepts any REQUEST
// packet that contains options for serverID and resquestedIP that match
// expSvrIP and expReqIP respectively. It responds with
// both an ACKNOWLEDGEMENT packet from a DHCP server as well as a NAK, in order
// to simulate a network with two conflicting servers.
func newRejectAndRespondToRequest(expReqIP, expSvrIP string, options optionMap, fields fieldMap, nakFirst bool) *dhcpHandlingRule {
rule := newRespondToRequest(expReqIP, expSvrIP, options, fields, true, "", "", true)
rule.respPktCnt = 2
rule.ruleType = rejectAndRespondToRequest
rule.nakFirst = nakFirst
return rule
}
// newAcceptDecline creates a handler that accepts any DECLINE packet that
// contains an option for serverID that matches expSvrIP. There is no
// response to this packet.
func newAcceptDecline(expSvrIP string, options optionMap, fields fieldMap) *dhcpHandlingRule {
return &dhcpHandlingRule{
ruleType: acceptDecline,
options: options,
fields: fields,
msgType: release,
respPktCnt: 1,
expSvrIP: expSvrIP,
}
}