mirror of
https://github.com/weaveworks/scope.git
synced 2026-07-18 21:09:38 +00:00
We'd like to benefit from the memory reduction from: https://github.com/google/gopacket/pull/377 I just ran: $ gvt update github.com/google/gopacket Fixes: https://github.com/weaveworks/scope/issues/2905
351 lines
10 KiB
Go
351 lines
10 KiB
Go
// Copyright 2013 Google, Inc. 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 ip4defrag implements a IPv4 defragmenter
|
|
package ip4defrag
|
|
|
|
import (
|
|
"container/list"
|
|
"errors"
|
|
"fmt"
|
|
"log"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/google/gopacket"
|
|
"github.com/google/gopacket/layers"
|
|
)
|
|
|
|
// Quick and Easy to use debug code to trace
|
|
// how defrag works.
|
|
var debug debugging = false // or flip to true
|
|
type debugging bool
|
|
|
|
func (d debugging) Printf(format string, args ...interface{}) {
|
|
if d {
|
|
log.Printf(format, args...)
|
|
}
|
|
}
|
|
|
|
// Constants determining how to handle fragments.
|
|
const (
|
|
IPv4MinimumFragmentSize = 576 // Minimum size of a single fragment
|
|
IPv4MaximumSize = 65535 // Maximum size of a fragment (2^16)
|
|
IPv4MaximumFragmentOffset = 8189 // Maximum offset of a fragment
|
|
IPv4MaximumFragmentListLen = 8 // Back out if we get more than this many fragments
|
|
)
|
|
|
|
// DefragIPv4 takes in an IPv4 packet with a fragment payload.
|
|
//
|
|
// It do not modify the IPv4 layer in place, 'in' remains untouched
|
|
// It returns a ready-to be used IPv4 layer.
|
|
//
|
|
// If the passed-in IPv4 layer is NOT fragmented, it will
|
|
// immediately return it without modifying the layer.
|
|
//
|
|
// If the IPv4 layer is a fragment and we don't have all
|
|
// fragments, it will return nil and store whatever internal
|
|
// information it needs to eventually defrag the packet.
|
|
//
|
|
// If the IPv4 layer is the last fragment needed to reconstruct
|
|
// the packet, a new IPv4 layer will be returned, and will be set to
|
|
// the entire defragmented packet,
|
|
//
|
|
// It use a map of all the running flows
|
|
//
|
|
// Usage example:
|
|
//
|
|
// func HandlePacket(in *layers.IPv4) err {
|
|
// defragger := ip4defrag.NewIPv4Defragmenter()
|
|
// in, err := defragger.DefragIPv4(in)
|
|
// if err != nil {
|
|
// return err
|
|
// } else if in == nil {
|
|
// return nil // packet fragment, we don't have whole packet yet.
|
|
// }
|
|
// // At this point, we know that 'in' is defragmented.
|
|
// //It may be the same 'in' passed to
|
|
// // HandlePacket, or it may not, but we don't really care :)
|
|
// ... do stuff to 'in' ...
|
|
//}
|
|
//
|
|
func (d *IPv4Defragmenter) DefragIPv4(in *layers.IPv4) (*layers.IPv4, error) {
|
|
return d.DefragIPv4WithTimestamp(in, time.Now())
|
|
}
|
|
|
|
// DefragIPv4WithTimestamp provides functionality of DefragIPv4 with
|
|
// an additional timestamp parameter which is used for discarding
|
|
// old fragments instead of time.Now()
|
|
//
|
|
// This is useful when operating on pcap files instead of live captured data
|
|
//
|
|
func (d *IPv4Defragmenter) DefragIPv4WithTimestamp(in *layers.IPv4, t time.Time) (*layers.IPv4, error) {
|
|
// check if we need to defrag
|
|
if st := d.dontDefrag(in); st == true {
|
|
debug.Printf("defrag: do nothing, do not need anything")
|
|
return in, nil
|
|
}
|
|
// perfom security checks
|
|
st, err := d.securityChecks(in)
|
|
if err != nil || st == false {
|
|
debug.Printf("defrag: alert security check")
|
|
return nil, err
|
|
}
|
|
|
|
// ok, got a fragment
|
|
debug.Printf("defrag: got a new fragment in.Id=%d in.FragOffset=%d in.Flags=%d\n",
|
|
in.Id, in.FragOffset*8, in.Flags)
|
|
|
|
// have we already seen a flow between src/dst with that Id?
|
|
ipf := newIPv4(in)
|
|
var fl *fragmentList
|
|
var exist bool
|
|
d.Lock()
|
|
fl, exist = d.ipFlows[ipf]
|
|
if !exist {
|
|
debug.Printf("defrag: unknown flow, creating a new one\n")
|
|
fl = new(fragmentList)
|
|
d.ipFlows[ipf] = fl
|
|
}
|
|
d.Unlock()
|
|
// insert, and if final build it
|
|
out, err2 := fl.insert(in, t)
|
|
|
|
// at last, if we hit the maximum frag list len
|
|
// without any defrag success, we just drop everything and
|
|
// raise an error
|
|
if out == nil && fl.List.Len()+1 > IPv4MaximumFragmentListLen {
|
|
d.flush(ipf)
|
|
return nil, fmt.Errorf("defrag: Fragment List hits its maximum"+
|
|
"size(%d), without success. Flushing the list",
|
|
IPv4MaximumFragmentListLen)
|
|
}
|
|
|
|
// if we got a packet, it's a new one, and he is defragmented
|
|
if out != nil {
|
|
// when defrag is done for a flow between two ip
|
|
// clean the list
|
|
d.flush(ipf)
|
|
return out, nil
|
|
}
|
|
return nil, err2
|
|
}
|
|
|
|
// DiscardOlderThan forgets all packets without any activity since
|
|
// time t. It returns the number of FragmentList aka number of
|
|
// fragment packets it has discarded.
|
|
func (d *IPv4Defragmenter) DiscardOlderThan(t time.Time) int {
|
|
var nb int
|
|
d.Lock()
|
|
for k, v := range d.ipFlows {
|
|
if v.LastSeen.Before(t) {
|
|
nb = nb + 1
|
|
delete(d.ipFlows, k)
|
|
}
|
|
}
|
|
d.Unlock()
|
|
return nb
|
|
}
|
|
|
|
// flush the fragment list for a particular flow
|
|
func (d *IPv4Defragmenter) flush(ipf ipv4) {
|
|
d.Lock()
|
|
fl := new(fragmentList)
|
|
d.ipFlows[ipf] = fl
|
|
d.Unlock()
|
|
}
|
|
|
|
// dontDefrag returns true if the IPv4 packet do not need
|
|
// any defragmentation
|
|
func (d *IPv4Defragmenter) dontDefrag(ip *layers.IPv4) bool {
|
|
// don't defrag packet with DF flag
|
|
if ip.Flags&layers.IPv4DontFragment != 0 {
|
|
return true
|
|
}
|
|
// don't defrag not fragmented ones
|
|
if ip.Flags&layers.IPv4MoreFragments == 0 && ip.FragOffset == 0 {
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
// securityChecks performs the needed security checks
|
|
func (d *IPv4Defragmenter) securityChecks(ip *layers.IPv4) (bool, error) {
|
|
// don't allow too big fragment offset
|
|
if ip.FragOffset > IPv4MaximumFragmentOffset {
|
|
return false, fmt.Errorf("defrag: fragment offset too big "+
|
|
"(handcrafted? %d > %d)", ip.FragOffset, IPv4MaximumFragmentOffset)
|
|
}
|
|
fragOffset := ip.FragOffset * 8
|
|
|
|
// don't allow fragment that would oversize an IP packet
|
|
if fragOffset+ip.Length > IPv4MaximumSize {
|
|
return false, fmt.Errorf("defrag: fragment will overrun "+
|
|
"(handcrafted? %d > %d)", ip.FragOffset*8+ip.Length, IPv4MaximumSize)
|
|
}
|
|
|
|
return true, nil
|
|
}
|
|
|
|
// fragmentList holds a container/list used to contains IP
|
|
// packets/fragments. It stores internal counters to track the
|
|
// maximum total of byte, and the current length it has received.
|
|
// It also stores a flag to know if he has seen the last packet.
|
|
type fragmentList struct {
|
|
List list.List
|
|
Highest uint16
|
|
Current uint16
|
|
FinalReceived bool
|
|
LastSeen time.Time
|
|
}
|
|
|
|
// insert insert an IPv4 fragment/packet into the Fragment List
|
|
// It use the following strategy : we are inserting fragment based
|
|
// on their offset, latest first. This is sometimes called BSD-Right.
|
|
// See: http://www.sans.org/reading-room/whitepapers/detection/ip-fragment-reassembly-scapy-33969
|
|
func (f *fragmentList) insert(in *layers.IPv4, t time.Time) (*layers.IPv4, error) {
|
|
// TODO: should keep a copy of *in in the list
|
|
// or not (ie the packet source is reliable) ? -> depends on Lazy / last packet
|
|
fragOffset := in.FragOffset * 8
|
|
if fragOffset >= f.Highest {
|
|
f.List.PushBack(in)
|
|
} else {
|
|
for e := f.List.Front(); e != nil; e = e.Next() {
|
|
frag, _ := e.Value.(*layers.IPv4)
|
|
if in.FragOffset == frag.FragOffset {
|
|
// TODO: what if we receive a fragment
|
|
// that begins with duplicate data but
|
|
// *also* has new data? For example:
|
|
//
|
|
// AAAA
|
|
// BB
|
|
// BBCC
|
|
// DDDD
|
|
//
|
|
// In this situation we completely
|
|
// ignore CC and the complete packet can
|
|
// never be reassembled.
|
|
debug.Printf("defrag: ignoring frag %d as we already have it (duplicate?)\n",
|
|
fragOffset)
|
|
return nil, nil
|
|
}
|
|
if in.FragOffset < frag.FragOffset {
|
|
debug.Printf("defrag: inserting frag %d before existing frag %d\n",
|
|
fragOffset, frag.FragOffset*8)
|
|
f.List.InsertBefore(in, e)
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
f.LastSeen = t
|
|
|
|
fragLength := in.Length - 20
|
|
// After inserting the Fragment, we update the counters
|
|
if f.Highest < fragOffset+fragLength {
|
|
f.Highest = fragOffset + fragLength
|
|
}
|
|
f.Current = f.Current + fragLength
|
|
|
|
debug.Printf("defrag: insert ListLen: %d Highest:%d Current:%d\n",
|
|
f.List.Len(),
|
|
f.Highest, f.Current)
|
|
|
|
// Final Fragment ?
|
|
if in.Flags&layers.IPv4MoreFragments == 0 {
|
|
f.FinalReceived = true
|
|
}
|
|
// Ready to try defrag ?
|
|
if f.FinalReceived && f.Highest == f.Current {
|
|
return f.build(in)
|
|
}
|
|
return nil, nil
|
|
}
|
|
|
|
// Build builds the final datagram, modifying ip in place.
|
|
// It puts priority to packet in the early position of the list.
|
|
// See Insert for more details.
|
|
func (f *fragmentList) build(in *layers.IPv4) (*layers.IPv4, error) {
|
|
var final []byte
|
|
var currentOffset uint16
|
|
|
|
debug.Printf("defrag: building the datagram \n")
|
|
for e := f.List.Front(); e != nil; e = e.Next() {
|
|
frag, _ := e.Value.(*layers.IPv4)
|
|
if frag.FragOffset*8 == currentOffset {
|
|
debug.Printf("defrag: building - adding %d\n", frag.FragOffset*8)
|
|
final = append(final, frag.Payload...)
|
|
currentOffset = currentOffset + frag.Length - 20
|
|
} else if frag.FragOffset*8 < currentOffset {
|
|
// overlapping fragment - let's take only what we need
|
|
startAt := currentOffset - frag.FragOffset*8
|
|
debug.Printf("defrag: building - overlapping, starting at %d\n",
|
|
startAt)
|
|
if startAt > frag.Length-20 {
|
|
return nil, errors.New("defrag: building - invalid fragment")
|
|
}
|
|
final = append(final, frag.Payload[startAt:]...)
|
|
currentOffset = currentOffset + frag.FragOffset*8
|
|
} else {
|
|
// Houston - we have an hole !
|
|
debug.Printf("defrag: hole found while building, " +
|
|
"stopping the defrag process\n")
|
|
return nil, errors.New("defrag: building - hole found")
|
|
}
|
|
debug.Printf("defrag: building - next is %d\n", currentOffset)
|
|
}
|
|
|
|
// TODO recompute IP Checksum
|
|
out := &layers.IPv4{
|
|
Version: in.Version,
|
|
IHL: in.IHL,
|
|
TOS: in.TOS,
|
|
Length: f.Highest,
|
|
Id: 0,
|
|
Flags: 0,
|
|
FragOffset: 0,
|
|
TTL: in.TTL,
|
|
Protocol: in.Protocol,
|
|
Checksum: 0,
|
|
SrcIP: in.SrcIP,
|
|
DstIP: in.DstIP,
|
|
Options: in.Options,
|
|
Padding: in.Padding,
|
|
}
|
|
out.Payload = final
|
|
|
|
return out, nil
|
|
}
|
|
|
|
// ipv4 is a struct to be used as a key.
|
|
type ipv4 struct {
|
|
ip4 gopacket.Flow
|
|
id uint16
|
|
}
|
|
|
|
// newIPv4 returns a new initialized IPv4 Flow
|
|
func newIPv4(ip *layers.IPv4) ipv4 {
|
|
return ipv4{
|
|
ip4: ip.NetworkFlow(),
|
|
id: ip.Id,
|
|
}
|
|
}
|
|
|
|
// IPv4Defragmenter is a struct which embedded a map of
|
|
// all fragment/packet.
|
|
type IPv4Defragmenter struct {
|
|
sync.RWMutex
|
|
ipFlows map[ipv4]*fragmentList
|
|
}
|
|
|
|
// NewIPv4Defragmenter returns a new IPv4Defragmenter
|
|
// with an initialized map.
|
|
func NewIPv4Defragmenter() *IPv4Defragmenter {
|
|
return &IPv4Defragmenter{
|
|
ipFlows: make(map[ipv4]*fragmentList),
|
|
}
|
|
}
|