326 lines
8.3 KiB
Go
326 lines
8.3 KiB
Go
|
// Copyright 2012 Google, Inc. All rights reserved.
|
||
|
// Copyright 2009-2011 Andreas Krennmair. All rights reserved.
|
||
|
//
|
||
|
// Use of this source code is governed by a BSD-style license
|
||
|
// that can be found in the LICENSE file in the root of the source
|
||
|
// tree.
|
||
|
|
||
|
package layers
|
||
|
|
||
|
import (
|
||
|
"encoding/binary"
|
||
|
"errors"
|
||
|
"fmt"
|
||
|
"net"
|
||
|
"strings"
|
||
|
|
||
|
"github.com/google/gopacket"
|
||
|
)
|
||
|
|
||
|
type IPv4Flag uint8
|
||
|
|
||
|
const (
|
||
|
IPv4EvilBit IPv4Flag = 1 << 2 // http://tools.ietf.org/html/rfc3514 ;)
|
||
|
IPv4DontFragment IPv4Flag = 1 << 1
|
||
|
IPv4MoreFragments IPv4Flag = 1 << 0
|
||
|
)
|
||
|
|
||
|
func (f IPv4Flag) String() string {
|
||
|
var s []string
|
||
|
if f&IPv4EvilBit != 0 {
|
||
|
s = append(s, "Evil")
|
||
|
}
|
||
|
if f&IPv4DontFragment != 0 {
|
||
|
s = append(s, "DF")
|
||
|
}
|
||
|
if f&IPv4MoreFragments != 0 {
|
||
|
s = append(s, "MF")
|
||
|
}
|
||
|
return strings.Join(s, "|")
|
||
|
}
|
||
|
|
||
|
// IPv4 is the header of an IP packet.
|
||
|
type IPv4 struct {
|
||
|
BaseLayer
|
||
|
Version uint8
|
||
|
IHL uint8
|
||
|
TOS uint8
|
||
|
Length uint16
|
||
|
Id uint16
|
||
|
Flags IPv4Flag
|
||
|
FragOffset uint16
|
||
|
TTL uint8
|
||
|
Protocol IPProtocol
|
||
|
Checksum uint16
|
||
|
SrcIP net.IP
|
||
|
DstIP net.IP
|
||
|
Options []IPv4Option
|
||
|
Padding []byte
|
||
|
}
|
||
|
|
||
|
// LayerType returns LayerTypeIPv4
|
||
|
func (i *IPv4) LayerType() gopacket.LayerType { return LayerTypeIPv4 }
|
||
|
func (i *IPv4) NetworkFlow() gopacket.Flow {
|
||
|
return gopacket.NewFlow(EndpointIPv4, i.SrcIP, i.DstIP)
|
||
|
}
|
||
|
|
||
|
type IPv4Option struct {
|
||
|
OptionType uint8
|
||
|
OptionLength uint8
|
||
|
OptionData []byte
|
||
|
}
|
||
|
|
||
|
func (i IPv4Option) String() string {
|
||
|
return fmt.Sprintf("IPv4Option(%v:%v)", i.OptionType, i.OptionData)
|
||
|
}
|
||
|
|
||
|
// for the current ipv4 options, return the number of bytes (including
|
||
|
// padding that the options used)
|
||
|
func (ip *IPv4) getIPv4OptionSize() uint8 {
|
||
|
optionSize := uint8(0)
|
||
|
for _, opt := range ip.Options {
|
||
|
switch opt.OptionType {
|
||
|
case 0:
|
||
|
// this is the end of option lists
|
||
|
optionSize++
|
||
|
case 1:
|
||
|
// this is the padding
|
||
|
optionSize++
|
||
|
default:
|
||
|
optionSize += opt.OptionLength
|
||
|
|
||
|
}
|
||
|
}
|
||
|
// make sure the options are aligned to 32 bit boundary
|
||
|
if (optionSize % 4) != 0 {
|
||
|
optionSize += 4 - (optionSize % 4)
|
||
|
}
|
||
|
return optionSize
|
||
|
}
|
||
|
|
||
|
// SerializeTo writes the serialized form of this layer into the
|
||
|
// SerializationBuffer, implementing gopacket.SerializableLayer.
|
||
|
func (ip *IPv4) SerializeTo(b gopacket.SerializeBuffer, opts gopacket.SerializeOptions) error {
|
||
|
optionLength := ip.getIPv4OptionSize()
|
||
|
bytes, err := b.PrependBytes(20 + int(optionLength))
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
if opts.FixLengths {
|
||
|
ip.IHL = 5 + (optionLength / 4)
|
||
|
ip.Length = uint16(len(b.Bytes()))
|
||
|
}
|
||
|
bytes[0] = (ip.Version << 4) | ip.IHL
|
||
|
bytes[1] = ip.TOS
|
||
|
binary.BigEndian.PutUint16(bytes[2:], ip.Length)
|
||
|
binary.BigEndian.PutUint16(bytes[4:], ip.Id)
|
||
|
binary.BigEndian.PutUint16(bytes[6:], ip.flagsfrags())
|
||
|
bytes[8] = ip.TTL
|
||
|
bytes[9] = byte(ip.Protocol)
|
||
|
if err := ip.AddressTo4(); err != nil {
|
||
|
return err
|
||
|
}
|
||
|
copy(bytes[12:16], ip.SrcIP)
|
||
|
copy(bytes[16:20], ip.DstIP)
|
||
|
|
||
|
curLocation := 20
|
||
|
// Now, we will encode the options
|
||
|
for _, opt := range ip.Options {
|
||
|
switch opt.OptionType {
|
||
|
case 0:
|
||
|
// this is the end of option lists
|
||
|
bytes[curLocation] = 0
|
||
|
curLocation++
|
||
|
case 1:
|
||
|
// this is the padding
|
||
|
bytes[curLocation] = 1
|
||
|
curLocation++
|
||
|
default:
|
||
|
bytes[curLocation] = opt.OptionType
|
||
|
bytes[curLocation+1] = opt.OptionLength
|
||
|
|
||
|
// sanity checking to protect us from buffer overrun
|
||
|
if len(opt.OptionData) > int(opt.OptionLength-2) {
|
||
|
return errors.New("option length is smaller than length of option data")
|
||
|
}
|
||
|
copy(bytes[curLocation+2:curLocation+int(opt.OptionLength)], opt.OptionData)
|
||
|
curLocation += int(opt.OptionLength)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if opts.ComputeChecksums {
|
||
|
ip.Checksum = checksum(bytes)
|
||
|
}
|
||
|
binary.BigEndian.PutUint16(bytes[10:], ip.Checksum)
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
func checksum(bytes []byte) uint16 {
|
||
|
// Clear checksum bytes
|
||
|
bytes[10] = 0
|
||
|
bytes[11] = 0
|
||
|
|
||
|
// Compute checksum
|
||
|
var csum uint32
|
||
|
for i := 0; i < len(bytes); i += 2 {
|
||
|
csum += uint32(bytes[i]) << 8
|
||
|
csum += uint32(bytes[i+1])
|
||
|
}
|
||
|
for {
|
||
|
// Break when sum is less or equals to 0xFFFF
|
||
|
if csum <= 65535 {
|
||
|
break
|
||
|
}
|
||
|
// Add carry to the sum
|
||
|
csum = (csum >> 16) + uint32(uint16(csum))
|
||
|
}
|
||
|
// Flip all the bits
|
||
|
return ^uint16(csum)
|
||
|
}
|
||
|
|
||
|
func (ip *IPv4) flagsfrags() (ff uint16) {
|
||
|
ff |= uint16(ip.Flags) << 13
|
||
|
ff |= ip.FragOffset
|
||
|
return
|
||
|
}
|
||
|
|
||
|
// DecodeFromBytes decodes the given bytes into this layer.
|
||
|
func (ip *IPv4) DecodeFromBytes(data []byte, df gopacket.DecodeFeedback) error {
|
||
|
if len(data) < 20 {
|
||
|
df.SetTruncated()
|
||
|
return fmt.Errorf("Invalid ip4 header. Length %d less than 20", len(data))
|
||
|
}
|
||
|
flagsfrags := binary.BigEndian.Uint16(data[6:8])
|
||
|
|
||
|
ip.Version = uint8(data[0]) >> 4
|
||
|
ip.IHL = uint8(data[0]) & 0x0F
|
||
|
ip.TOS = data[1]
|
||
|
ip.Length = binary.BigEndian.Uint16(data[2:4])
|
||
|
ip.Id = binary.BigEndian.Uint16(data[4:6])
|
||
|
ip.Flags = IPv4Flag(flagsfrags >> 13)
|
||
|
ip.FragOffset = flagsfrags & 0x1FFF
|
||
|
ip.TTL = data[8]
|
||
|
ip.Protocol = IPProtocol(data[9])
|
||
|
ip.Checksum = binary.BigEndian.Uint16(data[10:12])
|
||
|
ip.SrcIP = data[12:16]
|
||
|
ip.DstIP = data[16:20]
|
||
|
ip.Options = ip.Options[:0]
|
||
|
ip.Padding = nil
|
||
|
// Set up an initial guess for contents/payload... we'll reset these soon.
|
||
|
ip.BaseLayer = BaseLayer{Contents: data}
|
||
|
|
||
|
// This code is added for the following enviroment:
|
||
|
// * Windows 10 with TSO option activated. ( tested on Hyper-V, RealTek ethernet driver )
|
||
|
if ip.Length == 0 {
|
||
|
// If using TSO(TCP Segmentation Offload), length is zero.
|
||
|
// The actual packet length is the length of data.
|
||
|
ip.Length = uint16(len(data))
|
||
|
}
|
||
|
|
||
|
if ip.Length < 20 {
|
||
|
return fmt.Errorf("Invalid (too small) IP length (%d < 20)", ip.Length)
|
||
|
} else if ip.IHL < 5 {
|
||
|
return fmt.Errorf("Invalid (too small) IP header length (%d < 5)", ip.IHL)
|
||
|
} else if int(ip.IHL*4) > int(ip.Length) {
|
||
|
return fmt.Errorf("Invalid IP header length > IP length (%d > %d)", ip.IHL, ip.Length)
|
||
|
}
|
||
|
if cmp := len(data) - int(ip.Length); cmp > 0 {
|
||
|
data = data[:ip.Length]
|
||
|
} else if cmp < 0 {
|
||
|
df.SetTruncated()
|
||
|
if int(ip.IHL)*4 > len(data) {
|
||
|
return errors.New("Not all IP header bytes available")
|
||
|
}
|
||
|
}
|
||
|
ip.Contents = data[:ip.IHL*4]
|
||
|
ip.Payload = data[ip.IHL*4:]
|
||
|
// From here on, data contains the header options.
|
||
|
data = data[20 : ip.IHL*4]
|
||
|
// Pull out IP options
|
||
|
for len(data) > 0 {
|
||
|
if ip.Options == nil {
|
||
|
// Pre-allocate to avoid growing the slice too much.
|
||
|
ip.Options = make([]IPv4Option, 0, 4)
|
||
|
}
|
||
|
opt := IPv4Option{OptionType: data[0]}
|
||
|
switch opt.OptionType {
|
||
|
case 0: // End of options
|
||
|
opt.OptionLength = 1
|
||
|
ip.Options = append(ip.Options, opt)
|
||
|
ip.Padding = data[1:]
|
||
|
return nil
|
||
|
case 1: // 1 byte padding
|
||
|
opt.OptionLength = 1
|
||
|
data = data[1:]
|
||
|
ip.Options = append(ip.Options, opt)
|
||
|
default:
|
||
|
if len(data) < 2 {
|
||
|
df.SetTruncated()
|
||
|
return fmt.Errorf("Invalid ip4 option length. Length %d less than 2", len(data))
|
||
|
}
|
||
|
opt.OptionLength = data[1]
|
||
|
if len(data) < int(opt.OptionLength) {
|
||
|
df.SetTruncated()
|
||
|
return fmt.Errorf("IP option length exceeds remaining IP header size, option type %v length %v", opt.OptionType, opt.OptionLength)
|
||
|
}
|
||
|
if opt.OptionLength <= 2 {
|
||
|
return fmt.Errorf("Invalid IP option type %v length %d. Must be greater than 2", opt.OptionType, opt.OptionLength)
|
||
|
}
|
||
|
opt.OptionData = data[2:opt.OptionLength]
|
||
|
data = data[opt.OptionLength:]
|
||
|
ip.Options = append(ip.Options, opt)
|
||
|
}
|
||
|
}
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
func (i *IPv4) CanDecode() gopacket.LayerClass {
|
||
|
return LayerTypeIPv4
|
||
|
}
|
||
|
|
||
|
func (i *IPv4) NextLayerType() gopacket.LayerType {
|
||
|
if i.Flags&IPv4MoreFragments != 0 || i.FragOffset != 0 {
|
||
|
return gopacket.LayerTypeFragment
|
||
|
}
|
||
|
return i.Protocol.LayerType()
|
||
|
}
|
||
|
|
||
|
func decodeIPv4(data []byte, p gopacket.PacketBuilder) error {
|
||
|
ip := &IPv4{}
|
||
|
err := ip.DecodeFromBytes(data, p)
|
||
|
p.AddLayer(ip)
|
||
|
p.SetNetworkLayer(ip)
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
return p.NextDecoder(ip.NextLayerType())
|
||
|
}
|
||
|
|
||
|
func checkIPv4Address(addr net.IP) (net.IP, error) {
|
||
|
if c := addr.To4(); c != nil {
|
||
|
return c, nil
|
||
|
}
|
||
|
if len(addr) == net.IPv6len {
|
||
|
return nil, errors.New("address is IPv6")
|
||
|
}
|
||
|
return nil, fmt.Errorf("wrong length of %d bytes instead of %d", len(addr), net.IPv4len)
|
||
|
}
|
||
|
|
||
|
func (ip *IPv4) AddressTo4() error {
|
||
|
var src, dst net.IP
|
||
|
|
||
|
if addr, err := checkIPv4Address(ip.SrcIP); err != nil {
|
||
|
return fmt.Errorf("Invalid source IPv4 address (%s)", err)
|
||
|
} else {
|
||
|
src = addr
|
||
|
}
|
||
|
if addr, err := checkIPv4Address(ip.DstIP); err != nil {
|
||
|
return fmt.Errorf("Invalid destination IPv4 address (%s)", err)
|
||
|
} else {
|
||
|
dst = addr
|
||
|
}
|
||
|
ip.SrcIP = src
|
||
|
ip.DstIP = dst
|
||
|
return nil
|
||
|
}
|