<commit_before>// Copyright 2016 ETH Zurich
// Copyright 2018 ETH Zurich, Anapaya Systems
//
// 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.

// This file handles the path header (parsing/validating/updating/etc).

package rpkt

import (
	"hash"
	"time"

	"github.com/scionproto/scion/go/border/ifstate"
	"github.com/scionproto/scion/go/border/rcmn"
	"github.com/scionproto/scion/go/lib/assert"
	"github.com/scionproto/scion/go/lib/common"
	"github.com/scionproto/scion/go/lib/scmp"
	"github.com/scionproto/scion/go/lib/spath"
	"github.com/scionproto/scion/go/proto"
)

// validatePath validates the path header.
func (rp *RtrPkt) validatePath(dirFrom rcmn.Dir) error {
	// First check if there is a path
	if rp.infoF == nil || rp.hopF == nil {
		return common.NewBasicError("Path required",
			scmp.NewError(scmp.C_Path, scmp.T_P_PathRequired, nil, nil))
	}
	// There is a path, so ifCurr will be set
	if err := rp.validateLocalIF(rp.ifCurr); err != nil {
		return err
	}
	// Check for shorcuts in packets from core links
	if rp.infoF.Shortcut {
		currentLinkType := rp.Ctx.Conf.Net.IFs[*rp.ifCurr].Type
		if currentLinkType == proto.LinkType_core {
			return common.NewBasicError("Shortcut not allowed on core segment", nil)
		}
	}
	// A verify-only Hop Field cannot be used for routing.
	if rp.hopF.VerifyOnly {
		return common.NewBasicError("Hop field is VERIFY_ONLY",
			scmp.NewError(scmp.C_Path, scmp.T_P_NonRoutingHopF, rp.mkInfoPathOffsets(), nil))
	}
	// Check if Hop Field has expired.
	hopfExpiry := rp.infoF.Timestamp().Add(rp.hopF.ExpTime.ToDuration())
	if time.Now().After(hopfExpiry) {
		return common.NewBasicError(
			"Hop field expired",
			scmp.NewError(scmp.C_Path, scmp.T_P_ExpiredHopF, rp.mkInfoPathOffsets(), nil),
			"expiry", hopfExpiry,
		)
	}
	// Verify the Hop Field MAC.
	hfmac := rp.Ctx.Conf.HFMacPool.Get().(hash.Hash)
	err := rp.hopF.Verify(hfmac, rp.infoF.TsInt, rp.getHopFVer(dirFrom))
	rp.Ctx.Conf.HFMacPool.Put(hfmac)
	if err != nil && common.GetErrorMsg(err) == spath.ErrorHopFBadMac {
		err = scmp.NewError(scmp.C_Path, scmp.T_P_BadMac, rp.mkInfoPathOffsets(), err)
	}
	return err
}

// validateLocalIF makes sure a given interface ID exists in the local AS, and
// that it isn't revoked. Note that revocations are ignored if the packet's
// destination is this router.
func (rp *RtrPkt) validateLocalIF(ifid *common.IFIDType) error {
	if ifid == nil {
		return common.NewBasicError("validateLocalIF: Interface is nil", nil)
	}
	if _, ok := rp.Ctx.Conf.Topo.IFInfoMap[*ifid]; !ok {
		// No such interface.
		return common.NewBasicError(
			"Unknown IF",
			scmp.NewError(scmp.C_Path, scmp.T_P_BadIF, rp.mkInfoPathOffsets(), nil),
			"ifid", *ifid,
		)
	}
	for _, e := range rp.HBHExt {
		if e.Type() == common.ExtnOneHopPathType {
			// Ignore revocations if OneHopExtension is present
			return nil
		}
	}
	state, ok := ifstate.LoadState(*ifid)
	if !ok || state.Active {
		// Interface is not revoked
		return nil
	}
	// Interface is revoked.
	sRevInfo := state.SRevInfo
	if sRevInfo == nil {
		rp.Warn("No SRevInfo for revoked interface", "ifid", *ifid)
		return nil
	}
	revInfo, err := sRevInfo.RevInfo()
	if err != nil {
		rp.Warn("Could not parse RevInfo for interface", "ifid", *ifid, "err", err)
		return nil
	}
	err = revInfo.Active()
	if err != nil {
		if !common.IsTimeoutErr(err) {
			rp.Error("Error checking revocation", "err", err)
			return nil
		}
		// If the BR does not have a revocation for the current epoch, it considers
		// the interface as active until it receives a new revocation.
		newState := ifstate.NewInfo(*ifid, true, nil, nil)
		ifstate.UpdateIfNew(*ifid, state, newState)
		return nil
	}
	sinfo := scmp.NewInfoRevocation(
		uint16(rp.CmnHdr.CurrInfoF), uint16(rp.CmnHdr.CurrHopF), uint16(*ifid),
		rp.DirFrom == rcmn.DirExternal, state.RawSRev)
	return common.NewBasicError(
		errIntfRevoked,
		scmp.NewError(scmp.C_Path, scmp.T_P_RevokedIF, sinfo, nil),
		"ifid", ifid,
	)
}

// mkInfoPathOffsets is a helper function to create an scmp.InfoPathOffsets
// instance from the current packet.
func (rp *RtrPkt) mkInfoPathOffsets() scmp.Info {
	return &scmp.InfoPathOffsets{
		InfoF: uint16(rp.CmnHdr.CurrInfoF), HopF: uint16(rp.CmnHdr.CurrHopF),
		IfID: uint16(*rp.ifCurr), Ingress: rp.DirFrom == rcmn.DirExternal,
	}
}

// InfoF retrieves the current path Info Field if it isn't already known.
func (rp *RtrPkt) InfoF() (*spath.InfoField, error) {
	if rp.infoF == nil {
		for _, f := range rp.hooks.Infof {
			ret, infof, err := f()
			switch {
			case err != nil:
				return nil, err<commit_msg>border: check ifCurr before dereferencing it (#1986)<commit_after>// Copyright 2016 ETH Zurich
// Copyright 2018 ETH Zurich, Anapaya Systems
//
// 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.

// This file handles the path header (parsing/validating/updating/etc).

package rpkt

import (
	"hash"
	"time"

	"github.com/scionproto/scion/go/border/ifstate"
	"github.com/scionproto/scion/go/border/rcmn"
	"github.com/scionproto/scion/go/lib/assert"
	"github.com/scionproto/scion/go/lib/common"
	"github.com/scionproto/scion/go/lib/scmp"
	"github.com/scionproto/scion/go/lib/spath"
	"github.com/scionproto/scion/go/proto"
)

// validatePath validates the path header.
func (rp *RtrPkt) validatePath(dirFrom rcmn.Dir) error {
	// First check if there is a path
	if rp.infoF == nil || rp.hopF == nil {
		return common.NewBasicError("Path required",
			scmp.NewError(scmp.C_Path, scmp.T_P_PathRequired, nil, nil))
	}
	// There is a path, so ifCurr will be set
	if err := rp.validateLocalIF(rp.ifCurr); err != nil {
		return err
	}
	// Check for shorcuts in packets from core links
	if rp.infoF.Shortcut {
		currentLinkType := rp.Ctx.Conf.Net.IFs[*rp.ifCurr].Type
		if currentLinkType == proto.LinkType_core {
			return common.NewBasicError("Shortcut not allowed on core segment", nil)
		}
	}
	// A verify-only Hop Field cannot be used for routing.
	if rp.hopF.VerifyOnly {
		return common.NewBasicError("Hop field is VERIFY_ONLY",
			scmp.NewError(scmp.C_Path, scmp.T_P_NonRoutingHopF, rp.mkInfoPathOffsets(), nil))
	}
	// Check if Hop Field has expired.
	hopfExpiry := rp.infoF.Timestamp().Add(rp.hopF.ExpTime.ToDuration())
	if time.Now().After(hopfExpiry) {
		return common.NewBasicError(
			"Hop field expired",
			scmp.NewError(scmp.C_Path, scmp.T_P_ExpiredHopF, rp.mkInfoPathOffsets(), nil),
			"expiry", hopfExpiry,
		)
	}
	// Verify the Hop Field MAC.
	hfmac := rp.Ctx.Conf.HFMacPool.Get().(hash.Hash)
	err := rp.hopF.Verify(hfmac, rp.infoF.TsInt, rp.getHopFVer(dirFrom))
	rp.Ctx.Conf.HFMacPool.Put(hfmac)
	if err != nil && common.GetErrorMsg(err) == spath.ErrorHopFBadMac {
		err = scmp.NewError(scmp.C_Path, scmp.T_P_BadMac, rp.mkInfoPathOffsets(), err)
	}
	return err
}

// validateLocalIF makes sure a given interface ID exists in the local AS, and
// that it isn't revoked. Note that revocations are ignored if the packet's
// destination is this router.
func (rp *RtrPkt) validateLocalIF(ifid *common.IFIDType) error {
	if ifid == nil {
		return common.NewBasicError("validateLocalIF: Interface is nil", nil)
	}
	if _, ok := rp.Ctx.Conf.Topo.IFInfoMap[*ifid]; !ok {
		// No such interface.
		return common.NewBasicError(
			"Unknown IF",
			scmp.NewError(scmp.C_Path, scmp.T_P_BadIF, rp.mkInfoPathOffsets(), nil),
			"ifid", *ifid,
		)
	}
	for _, e := range rp.HBHExt {
		if e.Type() == common.ExtnOneHopPathType {
			// Ignore revocations if OneHopExtension is present
			return nil
		}
	}
	state, ok := ifstate.LoadState(*ifid)
	if !ok || state.Active {
		// Interface is not revoked
		return nil
	}
	// Interface is revoked.
	sRevInfo := state.SRevInfo
	if sRevInfo == nil {
		rp.Warn("No SRevInfo for revoked interface", "ifid", *ifid)
		return nil
	}
	revInfo, err := sRevInfo.RevInfo()
	if err != nil {
		rp.Warn("Could not parse RevInfo for interface", "ifid", *ifid, "err", err)
		return nil
	}
	err = revInfo.Active()
	if err != nil {
		if !common.IsTimeoutErr(err) {
			rp.Error("Error checking revocation", "err", err)
			return nil
		}
		// If the BR does not have a revocation for the current epoch, it considers
		// the interface as active until it receives a new revocation.
		newState := ifstate.NewInfo(*ifid, true, nil, nil)
		ifstate.UpdateIfNew(*ifid, state, newState)
		return nil
	}
	sinfo := scmp.NewInfoRevocation(
		uint16(rp.CmnHdr.CurrInfoF), uint16(rp.CmnHdr.CurrHopF), uint16(*ifid),
		rp.DirFrom == rcmn.DirExternal, state.RawSRev)
	return common.NewBasicError(
		errIntfRevoked,
		scmp.NewError(scmp.C_Path, scmp.T_P_RevokedIF, sinfo, nil),
		"ifid", ifid,
	)
}

// mkInfoPathOffsets is a helper function to create an scmp.InfoPathOffsets
// instance from the current packet.
func (rp *RtrPkt) mkInfoPathOffsets() scmp.Info {
	var ifid uint16
	if curr, err := rp.IFCurr(); curr != nil && err == nil {
		ifid = uint16(*curr)
	}
	return &scmp.InfoPathOffsets{
		InfoF: uint16(rp.CmnHdr.CurrInfoF), HopF: uint16(rp.CmnHdr.CurrHopF),
		IfID: ifid, Ingress: rp.DirFrom == rcmn.DirExternal,
	}
}

// InfoF retrieves the current path Info Field if it isn't already known.
func (rp *RtrPkt) InfoF() (*spath.InfoField, error) {
	if rp.infoF == nil {
		for _, f := range rp.hooks.Infof {
			ret, infof, err := f()
			switch {
			case err != nil:
				return nil, err
{'fraction_non_alphanumeric': 0.1272638753651412, 'fraction_numerical': 0.005258033106134372, 'mean_word_length': 3.808520599250936, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 3, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '2934763', 'n_tokens_mistral': 3994, 'n_tokens_neox': 3761, 'n_words': 1012}