From e9230f07018749c4fb8ed8356424566f00ef5681 Mon Sep 17 00:00:00 2001 From: garmr Date: Fri, 6 Oct 2023 09:41:53 -0700 Subject: [PATCH 1/8] Implement tamper action --- actions/tamper_action.go | 673 +++++++++++++++++++++++++++++++--- actions/tamper_action_test.go | 121 ++++++ 2 files changed, 746 insertions(+), 48 deletions(-) create mode 100644 actions/tamper_action_test.go diff --git a/actions/tamper_action.go b/actions/tamper_action.go index 10c7288..ebb8090 100644 --- a/actions/tamper_action.go +++ b/actions/tamper_action.go @@ -1,12 +1,29 @@ package actions import ( - "errors" + "encoding/binary" "fmt" + "math/rand" + "net" + "strconv" "strings" + "time" - "github.com/getlantern/geneva/internal/scanner" "github.com/google/gopacket" + "github.com/google/gopacket/layers" + + "github.com/getlantern/geneva/internal/scanner" +) + +// TODO: implement tamper actions for UDP + +const ( + // TamperReplace replaces the value of a packet field with the given value. + TamperReplace = iota + // TamperCorrupt replaces the value of a packet field with a randomly-generated value. + TamperCorrupt + // TamperAdd adds the value to a packet field. + TamperAdd ) // TamperMode describes the way that the "tamper" action can manipulate a packet. @@ -26,15 +43,6 @@ func (tm TamperMode) String() string { return "" } -const ( - // TamperReplace replaces the value of a packet field with the given value. - TamperReplace = iota - // TamperCorrupt replaces the value of a packet field with a randomly-generated value. - TamperCorrupt - // TamperAdd adds the value to a packet field. - TamperAdd -) - // TamperAction is a Geneva action that modifies packets (typically values in the packet header). type TamperAction struct { // Proto is the protocol layer where the modification will occur. @@ -50,11 +58,6 @@ type TamperAction struct { Action Action } -// Apply applies this action to the given packet. -func (a *TamperAction) Apply(packet gopacket.Packet) ([]gopacket.Packet, error) { - return nil, errors.New("tamper action unimplemented") -} - // String returns a string representation of this Action. func (a *TamperAction) String() string { newValue := "" @@ -86,30 +89,20 @@ func ParseTamperAction(s *scanner.Scanner) (Action, error) { return nil, fmt.Errorf("invalid fields for tamper rule: %s", str) } - action := &TamperAction{} + var ( + proto = strings.ToUpper(fields[0]) + field = strings.ToLower(fields[1]) - switch strings.ToLower(fields[0]) { - case "ip": - action.Proto = "IP" - case "tcp": - action.Proto = "TCP" - case "udp": - action.Proto = "UDP" - default: - return nil, fmt.Errorf( - "invalid tamper rule: %q is not a recognized protocol", - fields[0], - ) - } - - action.Field = fields[1] + mode TamperMode + newValue string + ) switch strings.ToLower(fields[2]) { case "replace": - action.Mode = TamperReplace - action.NewValue = fields[3] + mode = TamperReplace + newValue = fields[3] case "corrupt": - action.Mode = TamperCorrupt + mode = TamperCorrupt default: return nil, fmt.Errorf( "invalid tamper mode: %q must be either 'replace' or 'corrupt'", @@ -117,29 +110,613 @@ func ParseTamperAction(s *scanner.Scanner) (Action, error) { ) } - if _, err := s.Expect("("); err != nil { - action.Action = &SendAction{} + tamperAction := TamperAction{ + Proto: proto, + Field: field, + Mode: mode, + NewValue: newValue, + } + + if _, err := s.Expect("("); err == nil { + if tamperAction.Action, err = ParseAction(s); err != nil { + if c, err2 := s.Peek(); err2 == nil && c == ')' { + tamperAction.Action = &SendAction{} + } else { + return nil, fmt.Errorf("invalid action for tamper rule: %w", err) + } + } + + if _, err = s.Expect(","); err == nil { + if !s.FindToken(")", true) { + return nil, fmt.Errorf("tamper rules can only have one action") + } + } + + if _, err := s.Expect(")"); err != nil { + return nil, fmt.Errorf("unexpected token in tamper rule: %w", err) + } + } else { + tamperAction.Action = &SendAction{} + } + + switch proto { + case "IP": + return NewIPv4TamperAction(tamperAction) + case "TCP": + return NewTCPTamperAction(tamperAction) + case "UDP": + return NewUDPTamperAction(tamperAction) + default: + return nil, fmt.Errorf("invalid tamper rule: %q is not a recognized protocol", proto) + } +} + +// +// TCP Tamper Action +// + +type TCPField uint8 + +const ( + // supported TCP options. The other options are apparently obsolete and not used. + TCPOptionEol = layers.TCPOptionKindEndList // len 1 + TCPOptionNop = layers.TCPOptionKindNop // len 1 + TCPOptionMss = layers.TCPOptionKindMSS // len 4 + TCPOptionWscale = layers.TCPOptionKindWindowScale // len 3 + TCPOptionSackok = layers.TCPOptionKindSACKPermitted // len 2 + TCPOptionSack = layers.TCPOptionKindSACK // len 2+ + TCPOptionTimestamp = layers.TCPOptionKindTimestamps // len 10 + + // obsolete TCP options geneva uses and is in the strategies.txt file + TCPOptionAltCkhsum = 14 // len 3 + TCPOptionMd5Header = 19 // len 18 + TCPOptionUto = 28 // len 4 + + // putting fields after options so that we can use the gopacket.TCPOptionKind constants for options. + // this lets us use the same map for both fields and options and also directly compare + // tcpTamperAction.field == TCPOption when iterating over tcpPacket.Options + TCPFieldSrcPort = 9 + TCPFieldDstPort = 10 + TCPFieldSeq = 11 + TCPFieldAck = 12 + TCPFieldDataOff = 13 + TCPFieldFlags = 15 + TCPFieldWindow = 16 + TCPFieldUrgent = 17 + TCPLoad = 18 + + // TCP flag string representations for tamper rules + TCPFlagFin = "f" + TCPFlagSyn = "s" + TCPFlagRst = "r" + TCPFlagPsh = "p" + TCPFlagAck = "a" + TCPFlagUrg = "u" + TCPFlagEce = "e" + TCPFlagCwr = "c" + TCPFlagNop = "n" +) + +var ( + // tcpFields is a map of TCP fields to their corresponding TCPField constants. + // easier to use a map than a switch statement when parsing tamper rules. + tcpFields = map[string]TCPField{ + "srcport": TCPFieldSrcPort, + "dstport": TCPFieldDstPort, + "seq": TCPFieldSeq, + "ack": TCPFieldAck, + "dataofs": TCPFieldDataOff, + "flags": TCPFieldFlags, + "window": TCPFieldWindow, + "urgent": TCPFieldUrgent, + "options-eol": TCPOptionEol, + "options-nop": TCPOptionNop, + "options-mss": TCPOptionMss, + "options-wscale": TCPOptionWscale, + "options-sackok": TCPOptionSackok, + "options-sack": TCPOptionSack, + "options-timestamp": TCPOptionTimestamp, + "options-altchksum": TCPOptionTimestamp, + "options-md5header": TCPOptionMd5Header, + "options-uto": TCPOptionUto, + "load": TCPLoad, + } + + // tcpOptionLengths is a map of TCP options to the length of their data field. + tcpOptionLengths = map[TCPField]uint8{ + TCPOptionEol: 0, + TCPOptionNop: 0, + TCPOptionMss: 2, + TCPOptionWscale: 1, + TCPOptionSackok: 0, // the geneva team has this listed as 0, so at most the data is deleted + TCPOptionSack: 0, // same as above + TCPOptionTimestamp: 8, + TCPOptionAltCkhsum: 3, + TCPOptionMd5Header: 16, + TCPOptionUto: 2, + } +) + +// TCPTamperAction is a Geneva action that modifies TCP packets. +type TCPTamperAction struct { + // TamperAction is the underlying action parsed from the tamper rule. + TamperAction + // field is the TCP field to modify. + field TCPField + // valueGen is the value generator to use when modifying the field. + valueGen tamperValueGen +} + +// NewTCPTamperAction returns a new TCPTamperAction from the given TamperAction. +func NewTCPTamperAction(ta TamperAction) (*TCPTamperAction, error) { + field, ok := tcpFields[ta.Field] + if !ok { + return nil, fmt.Errorf("invalid tamper rule: %q is not a recognized TCP field", ta.Field) + } + + switch ta.Mode { + case TamperCorrupt: + r := rand.New(rand.NewSource(time.Now().UnixNano())) + return &TCPTamperAction{ + TamperAction: ta, + field: field, + valueGen: &tamperCorruptGen{r}, + }, nil + case TamperReplace: + gen := &tamperReplaceGen{} + switch { + case field == TCPFieldFlags: + gen.vUint = tcpFlagsToUint32(ta.NewValue) + case field < TCPFieldSrcPort: + // if field is an option, we need to convert the value to a byte slice + var b []byte + if val, err := strconv.ParseUint(ta.NewValue, 10, 64); err == nil { + b = make([]byte, 8) + binary.BigEndian.PutUint64(b, val) + b = b[8-tcpOptionLengths[field]:] + } else { + b = []byte(ta.NewValue) + } + + gen.vBytes = b + case field == TCPLoad: + gen.vBytes = []byte(ta.NewValue) + default: + val, err := strconv.ParseUint(ta.NewValue, 10, 32) + if err != nil { + return nil, fmt.Errorf("invalid tamper rule: %q is not a valid value for field %q", ta.NewValue, ta.Field) + } + + gen.vUint = uint32(val) + } + + return &TCPTamperAction{ + TamperAction: ta, + field: field, + valueGen: gen, + }, nil + } + + return nil, fmt.Errorf("invalid tamper rule: %q is not a valid tamper mode for TCP", ta.Mode) +} + +func (a *TCPTamperAction) Apply(packet gopacket.Packet) ([]gopacket.Packet, error) { + tcp := packet.Layer(layers.LayerTypeTCP).(*layers.TCP) + if tcp == nil { + return nil, fmt.Errorf("packet does not have a TCP layer") + } - return action, nil //nolint:nilerr + tamperTCP(tcp, a.field, a.valueGen) + + // if tampering with TCP options, we need to update the data offset and checksum + if strings.HasPrefix(a.Field, "options") { + updateTCPDataOffAndChksum(tcp) + if ip := packet.Layer(layers.LayerTypeIPv4).(*layers.IPv4); ip != nil { + updateIPv4LengthAndChksum(ip) + } } - if action.Action, err = ParseAction(s); err != nil { - if c, err2 := s.Peek(); err2 == nil && c == ')' { - action.Action = &SendAction{} + return a.Action.Apply(packet) +} + +// tamperTCP modifies the given TCP field using the given value generator. +func tamperTCP(tcp *layers.TCP, field TCPField, valueGen tamperValueGen) error { + switch field { + case TCPFieldSrcPort: + tcp.SrcPort = layers.TCPPort(valueGen.uint(16)) + case TCPFieldDstPort: + tcp.DstPort = layers.TCPPort(valueGen.uint(16)) + case TCPFieldSeq: + tcp.Seq = valueGen.uint(32) + case TCPFieldAck: + tcp.Ack = valueGen.uint(32) + case TCPFieldDataOff: + tcp.DataOffset = uint8(valueGen.uint(8)) + case TCPFieldWindow: + tcp.Window = uint16(valueGen.uint(16)) + case TCPFieldUrgent: + tcp.Urgent = uint16(valueGen.uint(16)) + case TCPFieldFlags: + setTCPFlags(tcp, uint16(valueGen.uint(16))) + case TCPLoad: + tcp.Payload = valueGen.bytes(1480) + default: + // find option in TCP header + var opt *layers.TCPOption + for i, o := range tcp.Options { + if field == TCPField(o.OptionType) { + opt = &tcp.Options[i] + break + } + } + + // create option if it doesn't exist + if opt == nil { + opt = &layers.TCPOption{ + OptionType: layers.TCPOptionKind(field), + } + tcp.Options = append(tcp.Options, *opt) + } + + opt.OptionData = valueGen.bytes(int(tcpOptionLengths[field])) + if field == TCPOptionEol || field == TCPOptionNop { + opt.OptionLength = 1 } else { - return nil, fmt.Errorf("invalid action for tamper rule: %w", err) + opt.OptionLength = uint8(len(opt.OptionData)) + 2 } + } - if _, err = s.Expect(","); err == nil { - if !s.FindToken(")", true) { - return nil, errors.New("tamper rules can only have one action") + // let gopacket handle converting the modified TCP headers into []byte for us since we changed the struct fields + // instead of the underlying []byte directly. SerializeTo doesn't write the changes to the raw packet + // so we have to copy the formatted bytes back into the packet header. + sb := gopacket.NewSerializeBuffer() + tcp.SerializeTo(sb, gopacket.SerializeOptions{}) + tcp.Contents = make([]byte, len(sb.Bytes())) + copy(tcp.Contents, sb.Bytes()) + + return nil +} + +// tcpFlagsToUint32 converts a string of TCP flags to a uint32 bitmap. +func tcpFlagsToUint32(flags string) uint32 { + flags = strings.ToLower(flags) + var f uint32 + for _, c := range flags { + switch c { + case 'f': + f |= 0x0001 + case 's': + f |= 0x0002 + case 'r': + f |= 0x0004 + case 'p': + f |= 0x0008 + case 'a': + f |= 0x0010 + case 'u': + f |= 0x0020 + case 'e': + f |= 0x0040 + case 'c': + f |= 0x0080 + case 'n': + f |= 0x0100 + } + } + return f +} + +// setTCPFlags sets the tcp struct fields using flags as a bitmap, does not modify the raw packet bytes. +func setTCPFlags(tcp *layers.TCP, flags uint16) { + tcp.FIN = flags&0x0001 != 0 + tcp.SYN = flags&0x0002 != 0 + tcp.RST = flags&0x0004 != 0 + tcp.PSH = flags&0x0008 != 0 + tcp.ACK = flags&0x0010 != 0 + tcp.URG = flags&0x0020 != 0 + tcp.ECE = flags&0x0040 != 0 + tcp.CWR = flags&0x0080 != 0 + tcp.NS = flags&0x0100 != 0 +} + +// updateTCPDataOffAndChksum updates the TCP data offset and checksum fields on the TCP struct and in the raw packet bytes. +func updateTCPDataOffAndChksum(tcp *layers.TCP) { + // update data offset + headerLen := len(tcp.Contents) + tcp.DataOffset = uint8(headerLen / 4) + tcp.Contents[12] = tcp.DataOffset << 4 + + // update checksum. + // the ComputeChecksum method requires the checksum bytes in the raw packet to be zeroed out. + tcp.Contents[16] = 0 + tcp.Contents[17] = 0 + chksum, _ := tcp.ComputeChecksum() + tcp.Checksum = chksum + binary.BigEndian.PutUint16(tcp.Contents[16:18], chksum) +} + +// +// IPv4 Tamper Action +// + +type IPv4Field uint8 + +const ( + // ip field constants + IPv4FieldSrcIP = iota + IPv4FieldDstIP + IPv4FieldVersion + IPv4FieldIHL + IPv4FieldTOS + IPv4FieldLength + IPv4FieldID + IPv4FieldFlags + IPv4FieldFragOffset + IPv4FieldTTL + IPv4FieldProtocol + IPv4FieldChecksum + IPv4Load +) + +var ( + ipv4Fields = map[string]IPv4Field{ + "srcip": IPv4FieldSrcIP, + "dstip": IPv4FieldDstIP, + "verion": IPv4FieldVersion, + "ihl": IPv4FieldIHL, + "tos": IPv4FieldTOS, + "length": IPv4FieldLength, + "id": IPv4FieldID, + // + // I don't know what the flags will look like in a tamper rule + // shouldn't be a problem since there isn't any tamper rules for flags currently + // "flags": IPv4FieldFlags, + // + "fragoffset": IPv4FieldFragOffset, + "ttl": IPv4FieldTTL, + "protocol": IPv4FieldProtocol, + "checksum": IPv4FieldChecksum, + "load": IPv4Load, + } +) + +// IPv4TamperAction is a Geneva action that modifies IPv4 packets. +type IPv4TamperAction struct { + // TamperAction is the underlying action parsed from the tamper rule. + TamperAction + // field is the IPv4 field to modify. + field IPv4Field + // valueGen is the value generator to use when modifying the field. + valueGen tamperValueGen +} + +// NewIPv4TamperAction returns a new IPv4TamperAction from the given TamperAction. +func NewIPv4TamperAction(ta TamperAction) (*IPv4TamperAction, error) { + field, ok := ipv4Fields[ta.Field] + if !ok { + return nil, fmt.Errorf("invalid tamper rule: %q is not a recognized IPv4 field", ta.Field) + } + + switch ta.Mode { + case TamperCorrupt: + r := rand.New(rand.NewSource(time.Now().UnixNano())) + return &IPv4TamperAction{ + TamperAction: ta, + field: field, + valueGen: &tamperCorruptGen{r}, + }, nil + case TamperReplace: + gen := &tamperReplaceGen{} + switch field { + case IPv4FieldSrcIP, IPv4FieldDstIP: + // parse IP address from NewValue and convert to []byte + ip := net.ParseIP(ta.NewValue) + if ip == nil { + return nil, fmt.Errorf("invalid tamper rule: %q is not a valid IPv4 address", ta.NewValue) + } + + if ip.To4() == nil { + return nil, fmt.Errorf("invalid tamper rule: IPv6 is not supported") + } + + gen.vBytes = ip + case IPv4Load: + gen.vBytes = []byte(ta.NewValue) + default: + // parse uint from NewValue + val, err := strconv.ParseUint(ta.NewValue, 10, 32) + if err != nil { + return nil, fmt.Errorf("invalid tamper rule: %q is not a valid value for field %q", ta.NewValue, ta.Field) + } + + gen.vUint = uint32(val) } + + return &IPv4TamperAction{ + TamperAction: ta, + field: field, + valueGen: gen, + }, nil } - if _, err := s.Expect(")"); err != nil { - return nil, fmt.Errorf("unexpected token in tamper rule: %w", err) + return nil, fmt.Errorf("invalid tamper rule: %q is not a valid tamper mode for IPv4", ta.Mode) +} + +func (a *IPv4TamperAction) Apply(packet gopacket.Packet) ([]gopacket.Packet, error) { + ip := packet.Layer(layers.LayerTypeIPv4).(*layers.IPv4) + if ip == nil { + return nil, fmt.Errorf("packet does not have a IPv4 layer") } - return action, nil + tamperIPv4(ip, a.field, a.valueGen) + + return a.Action.Apply(packet) +} + +// tamperIPv4 modifies the given IP field using the given value generator. +func tamperIPv4(ip *layers.IPv4, field IPv4Field, valueGen tamperValueGen) error { + switch field { + case IPv4FieldSrcIP: + ip.SrcIP = valueGen.bytes(4) + case IPv4FieldDstIP: + ip.DstIP = valueGen.bytes(4) + case IPv4FieldVersion: + ip.Version = uint8(valueGen.uint(8)) + case IPv4FieldIHL: + ip.IHL = uint8(valueGen.uint(8)) + case IPv4FieldTOS: + ip.TOS = uint8(valueGen.uint(8)) + case IPv4FieldLength: + ip.Length = uint16(valueGen.uint(16)) + case IPv4FieldFlags: + // TODO: maybe implement this? + case IPv4FieldFragOffset: + ip.FragOffset = uint16(valueGen.uint(16)) + case IPv4FieldTTL: + ip.TTL = uint8(valueGen.uint(8)) + case IPv4FieldProtocol: + ip.Protocol = layers.IPProtocol(valueGen.uint(8)) + case IPv4FieldChecksum: + ip.Checksum = uint16(valueGen.uint(16)) + case IPv4Load: + ip.Payload = valueGen.bytes(1480) + } + + // let gopacket handle converting modified packet into []byte again, it's just easier + // again copy the bytes back into the packet header + sb := gopacket.NewSerializeBuffer() + ip.SerializeTo(sb, gopacket.SerializeOptions{}) + ip.Contents = make([]byte, len(sb.Bytes())) + copy(ip.Contents, sb.Bytes()) + + // do we update length and checksum??? + // it doesn't look like the geneva team does when they tamper with the IP layer + + return nil +} + +func updateIPv4LengthAndChksum(ip *layers.IPv4) { + length := len(ip.Contents) + len(ip.Payload) + ip.Length = uint16(length) + binary.BigEndian.PutUint16(ip.Contents[2:4], ip.Length) + buf := make([]byte, length) + copy(buf, ip.Contents) + copy(buf[len(ip.Contents):], ip.Payload) + chksum := checksum(buf) + ip.Checksum = chksum + binary.BigEndian.PutUint16(ip.Contents[10:12], chksum) +} + +// copied directly from gopacket/layers/ip4.go because they didn't export one. for whatever some reason.. +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) +} + +// +// UDP Tamper Action +// + +type UDPField uint8 + +const ( +// udp field constants +) + +var ( + udpFields = map[string]UDPField{} +) + +// UDPTamperAction is a Geneva action that modifies UDP packets. +type UDPTamperAction struct { + // TamperAction is the underlying action parsed from the tamper rule. + TamperAction + // field is the UDP field to modify. + field UDPField + // valueGen is the value generator to use when modifying the field. + valueGen tamperValueGen +} + +// NewUDPTamperAction returns a new UDPTamperAction from the given TamperAction. +func NewUDPTamperAction(ta TamperAction) (*UDPTamperAction, error) { + return nil, fmt.Errorf("UDP tamper action unimplemented") +} + +func (a *UDPTamperAction) Apply(packet gopacket.Packet) ([]gopacket.Packet, error) { + udp := packet.Layer(layers.LayerTypeUDP).(*layers.UDP) + if udp == nil { + return nil, fmt.Errorf("packet does not have a UDP layer") + } + + tamperUDP(udp, a.field, a.valueGen) + + return a.Action.Apply(packet) +} + +// tamperUDP modifies the given UDP field using the given value generator. +func tamperUDP(tcp *layers.UDP, field UDPField, valueGen tamperValueGen) error { + return fmt.Errorf("tamper UDP not implemented") +} + +type tamperValueGen interface { + uint(bitSize int) uint32 + bytes(n int) []byte +} + +// tamperReplaceGen just returns newValue casted to the appropriate type. +// it assumes that newValue is the correct type. +type tamperReplaceGen struct { + vUint uint32 + vBytes []byte +} + +func (g *tamperReplaceGen) uint(bitSize int) uint32 { + return g.vUint +} + +func (g *tamperReplaceGen) bytes(n int) []byte { + if n == 0 { + return []byte{} + } + return append([]byte{}, g.vBytes...) +} + +// tamperCorruptGen generates random values for tamperCorrupt actions. +type tamperCorruptGen struct { + r *rand.Rand +} + +func (g *tamperCorruptGen) uint(bitSize int) uint32 { + n := g.r.Intn(1< 20 { + n = g.r.Intn(n) + } + b := make([]byte, n) + g.r.Read(b) + return b } diff --git a/actions/tamper_action_test.go b/actions/tamper_action_test.go new file mode 100644 index 0000000..7d1cde3 --- /dev/null +++ b/actions/tamper_action_test.go @@ -0,0 +1,121 @@ +package actions + +import ( + "reflect" + "testing" + + "github.com/google/gopacket" + "github.com/google/gopacket/layers" + + "github.com/getlantern/geneva/internal/scanner" +) + +func TestParseTamperAction(t *testing.T) { + tests := []struct { + name string + rule string + want Action + wantErr bool + }{ + { + name: "TCP tamper action replace uint", + rule: "tamper{TCP:dataofs:replace:10}", + want: &TCPTamperAction{ + TamperAction: TamperAction{ + Proto: "TCP", + Field: "dataofs", + NewValue: "10", + Mode: TamperReplace, + Action: &SendAction{}, + }, + field: TCPFieldDataOff, + valueGen: &tamperReplaceGen{vUint: 10}, + }, + wantErr: false, + }, + { + name: "TCP tamper action replace bytes", + rule: "tamper{TCP:options-mss:replace:15}", + want: &TCPTamperAction{ + TamperAction: TamperAction{ + Proto: "TCP", + Field: "options-mss", + NewValue: "15", + Mode: TamperReplace, + Action: &SendAction{}, + }, + field: TCPOptionMss, + valueGen: &tamperReplaceGen{vBytes: []byte{0x00, 0x0f}}, + }, + wantErr: false, + }, + { + name: "IPv4 tamper action replace uint", + rule: "tamper{IP:ttl:replace:15}", + want: &IPv4TamperAction{ + TamperAction: TamperAction{ + Proto: "IP", + Field: "ttl", + NewValue: "15", + Mode: TamperReplace, + Action: &SendAction{}, + }, + field: IPv4FieldTTL, + valueGen: &tamperReplaceGen{vUint: 15}, + }, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + s := scanner.NewScanner(tt.rule) + got, err := ParseTamperAction(s) + if (err != nil) != tt.wantErr { + t.Errorf("ParseTamperAction() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("ParseTamperAction() = %#v, want %#v", got, tt.want) + } + }) + } +} + +func TestTamperTCPOptions(t *testing.T) { + type args struct { + tcp *layers.TCP + field TCPField + valueGen tamperValueGen + } + tests := []struct { + name string + args args + wantErr bool + }{ + { + name: "", + args: args{}, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if err := tamperTCP(tt.args.tcp, tt.args.field, tt.args.valueGen); (err != nil) != tt.wantErr { + t.Errorf("tamperTCP() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} + +func tcpTestPkt() *layers.TCP { + tcpBytes := []byte{ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x08, 0x00, 0x45, 0x00, + 0x00, 0x34, 0x00, 0x00, 0x00, 0x00, 0x80, 0x06, 0xb9, 0x70, 0xc0, 0xa8, 0x00, 0x01, 0xc0, 0xa8, + 0x00, 0x02, 0x30, 0x39, 0xd4, 0x31, 0xde, 0xad, 0xbe, 0xef, 0x00, 0x00, 0x00, 0x00, 0x70, 0x02, + 0x00, 0x00, 0x82, 0x9c, 0x00, 0x00, 0x02, 0x04, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x54, 0x65, + 0x73, 0x74, + } + + pkt := gopacket.NewPacket(tcpBytes, layers.LinkTypeEthernet, gopacket.Default) + return pkt.Layer(layers.LayerTypeTCP).(*layers.TCP) +} From 2db6ed0e210519dbdb3a9a8b91c55465f9adc62f Mon Sep 17 00:00:00 2001 From: garmr Date: Mon, 9 Oct 2023 08:53:48 -0700 Subject: [PATCH 2/8] tamper TCP test --- actions/tamper_action.go | 21 +++++++++-------- actions/tamper_action_test.go | 43 +++++++++++++++++++++++++++-------- go.mod | 7 ++++++ go.sum | 9 ++++++++ 4 files changed, 61 insertions(+), 19 deletions(-) diff --git a/actions/tamper_action.go b/actions/tamper_action.go index ebb8090..f48dc15 100644 --- a/actions/tamper_action.go +++ b/actions/tamper_action.go @@ -223,7 +223,7 @@ var ( } // tcpOptionLengths is a map of TCP options to the length of their data field. - tcpOptionLengths = map[TCPField]uint8{ + tcpOptionLengths = map[TCPField]int{ TCPOptionEol: 0, TCPOptionNop: 0, TCPOptionMss: 2, @@ -320,7 +320,7 @@ func (a *TCPTamperAction) Apply(packet gopacket.Packet) ([]gopacket.Packet, erro } // tamperTCP modifies the given TCP field using the given value generator. -func tamperTCP(tcp *layers.TCP, field TCPField, valueGen tamperValueGen) error { +func tamperTCP(tcp *layers.TCP, field TCPField, valueGen tamperValueGen) { switch field { case TCPFieldSrcPort: tcp.SrcPort = layers.TCPPort(valueGen.uint(16)) @@ -350,19 +350,22 @@ func tamperTCP(tcp *layers.TCP, field TCPField, valueGen tamperValueGen) error { } } - // create option if it doesn't exist + // create option if it doesn't exist and move options-eol to the end of the list if opt == nil { - opt = &layers.TCPOption{ + tcp.Options = append(tcp.Options, layers.TCPOption{ OptionType: layers.TCPOptionKind(field), - } - tcp.Options = append(tcp.Options, *opt) + }) + + ol := len(tcp.Options) + tcp.Options[ol-2], tcp.Options[ol-1] = tcp.Options[ol-1], tcp.Options[ol-2] + opt = &tcp.Options[ol-2] } - opt.OptionData = valueGen.bytes(int(tcpOptionLengths[field])) + opt.OptionData = valueGen.bytes(tcpOptionLengths[field]) if field == TCPOptionEol || field == TCPOptionNop { opt.OptionLength = 1 } else { - opt.OptionLength = uint8(len(opt.OptionData)) + 2 + opt.OptionLength = uint8(tcpOptionLengths[field]) + 2 } } @@ -374,8 +377,6 @@ func tamperTCP(tcp *layers.TCP, field TCPField, valueGen tamperValueGen) error { tcp.SerializeTo(sb, gopacket.SerializeOptions{}) tcp.Contents = make([]byte, len(sb.Bytes())) copy(tcp.Contents, sb.Bytes()) - - return nil } // tcpFlagsToUint32 converts a string of TCP flags to a uint32 bitmap. diff --git a/actions/tamper_action_test.go b/actions/tamper_action_test.go index 7d1cde3..4090c1b 100644 --- a/actions/tamper_action_test.go +++ b/actions/tamper_action_test.go @@ -7,6 +7,8 @@ import ( "github.com/google/gopacket" "github.com/google/gopacket/layers" + "github.com/stretchr/testify/assert" + "github.com/getlantern/geneva/internal/scanner" ) @@ -88,21 +90,44 @@ func TestTamperTCPOptions(t *testing.T) { valueGen tamperValueGen } tests := []struct { - name string - args args - wantErr bool + name string + args args + want []byte }{ { - name: "", - args: args{}, - wantErr: false, + name: "tcp tamper replace existing option", + args: args{ + tcp: tcpTestPkt(), + field: TCPOptionMss, + valueGen: &tamperReplaceGen{vBytes: []byte{0x0f, 0xff}}, + }, + want: []byte{ + 0x30, 0x39, 0xd4, 0x31, 0xde, 0xad, 0xbe, 0xef, 0x00, 0x00, 0x00, 0x00, 0x70, 0x02, 0x00, + 0x00, 0x82, 0x9c, 0x00, 0x00, 0x02, 0x04, 0x0f, 0xff, 0x00, 0x00, 0x00, 0x00, 0x54, 0x65, + 0x73, 0x74, + }, + }, + { + name: "tcp tamper replace missing option", + args: args{ + tcp: tcpTestPkt(), + field: TCPOptionAltCkhsum, + valueGen: &tamperReplaceGen{vBytes: []byte{0xff, 0xff, 0xff}}, + }, + want: []byte{ + 0x30, 0x39, 0xd4, 0x31, 0xde, 0xad, 0xbe, 0xef, 0x00, 0x00, 0x00, 0x00, 0x70, 0x02, 0x00, + 0x00, 0x82, 0x9c, 0x00, 0x00, 0x02, 0x04, 0x20, 0x00, 0x0e, 0x05, 0xff, 0xff, 0xff, 0x00, + 0x00, 0x00, 0x00, 0x54, 0x65, 0x73, 0x74, + }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - if err := tamperTCP(tt.args.tcp, tt.args.field, tt.args.valueGen); (err != nil) != tt.wantErr { - t.Errorf("tamperTCP() error = %v, wantErr %v", err, tt.wantErr) - } + tamperTCP(tt.args.tcp, tt.args.field, tt.args.valueGen) + + got := append([]byte{}, tt.args.tcp.Contents...) + got = append(got, tt.args.tcp.Payload...) + assert.Equal(t, tt.want, got) }) } } diff --git a/go.mod b/go.mod index 3046c0b..824d7ef 100644 --- a/go.mod +++ b/go.mod @@ -3,3 +3,10 @@ module github.com/getlantern/geneva go 1.17 require github.com/google/gopacket v1.1.19 + +require ( + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/stretchr/testify v1.8.4 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) diff --git a/go.sum b/go.sum index 29f572a..3dc724f 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,11 @@ +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/google/gopacket v1.1.19 h1:ves8RnFZPGiFnTS0uPQStjwru6uO6h+nlr9j6fL7kF8= github.com/google/gopacket v1.1.19/go.mod h1:iJ8V8n6KS+z2U1A8pUwu8bW5SyEMkXJB8Yo/Vo+TKTo= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= @@ -12,3 +18,6 @@ golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= From 302defc7adc815e7c7e3cacc6e5530cc7ed2124e Mon Sep 17 00:00:00 2001 From: garmr Date: Mon, 9 Oct 2023 13:04:21 -0700 Subject: [PATCH 3/8] test edit payload --- actions/tamper_action.go | 2 ++ actions/tamper_action_test.go | 37 +++++++++++++++++++++++------------ 2 files changed, 27 insertions(+), 12 deletions(-) diff --git a/actions/tamper_action.go b/actions/tamper_action.go index f48dc15..0a9f847 100644 --- a/actions/tamper_action.go +++ b/actions/tamper_action.go @@ -633,6 +633,8 @@ func checksum(bytes []byte) uint16 { return ^uint16(csum) } +// Should we implement tamper for UDP?? + // // UDP Tamper Action // diff --git a/actions/tamper_action_test.go b/actions/tamper_action_test.go index 4090c1b..f479d3a 100644 --- a/actions/tamper_action_test.go +++ b/actions/tamper_action_test.go @@ -34,8 +34,7 @@ func TestParseTamperAction(t *testing.T) { valueGen: &tamperReplaceGen{vUint: 10}, }, wantErr: false, - }, - { + }, { name: "TCP tamper action replace bytes", rule: "tamper{TCP:options-mss:replace:15}", want: &TCPTamperAction{ @@ -50,8 +49,7 @@ func TestParseTamperAction(t *testing.T) { valueGen: &tamperReplaceGen{vBytes: []byte{0x00, 0x0f}}, }, wantErr: false, - }, - { + }, { name: "IPv4 tamper action replace uint", rule: "tamper{IP:ttl:replace:15}", want: &IPv4TamperAction{ @@ -83,7 +81,7 @@ func TestParseTamperAction(t *testing.T) { } } -func TestTamperTCPOptions(t *testing.T) { +func TestTamperTCP(t *testing.T) { type args struct { tcp *layers.TCP field TCPField @@ -97,7 +95,7 @@ func TestTamperTCPOptions(t *testing.T) { { name: "tcp tamper replace existing option", args: args{ - tcp: tcpTestPkt(), + tcp: testPkt().Layer(layers.LayerTypeTCP).(*layers.TCP), field: TCPOptionMss, valueGen: &tamperReplaceGen{vBytes: []byte{0x0f, 0xff}}, }, @@ -106,11 +104,10 @@ func TestTamperTCPOptions(t *testing.T) { 0x00, 0x82, 0x9c, 0x00, 0x00, 0x02, 0x04, 0x0f, 0xff, 0x00, 0x00, 0x00, 0x00, 0x54, 0x65, 0x73, 0x74, }, - }, - { + }, { name: "tcp tamper replace missing option", args: args{ - tcp: tcpTestPkt(), + tcp: testPkt().Layer(layers.LayerTypeTCP).(*layers.TCP), field: TCPOptionAltCkhsum, valueGen: &tamperReplaceGen{vBytes: []byte{0xff, 0xff, 0xff}}, }, @@ -119,8 +116,25 @@ func TestTamperTCPOptions(t *testing.T) { 0x00, 0x82, 0x9c, 0x00, 0x00, 0x02, 0x04, 0x20, 0x00, 0x0e, 0x05, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x54, 0x65, 0x73, 0x74, }, + }, { + name: "tcp tamper replace payload", + args: args{ + tcp: testPkt().Layer(layers.LayerTypeTCP).(*layers.TCP), + field: TCPLoad, + valueGen: &tamperReplaceGen{ + vBytes: []byte{ + 0x6d, 0x69, 0x73, 0x73, 0x20, 0x79, 0x6f, 0x75, 0x20, 0x46, 0x61, 0x77, 0x6b, 0x73, + }, + }, + }, + want: []byte{ + 0x30, 0x39, 0xd4, 0x31, 0xde, 0xad, 0xbe, 0xef, 0x00, 0x00, 0x00, 0x00, 0x70, 0x02, 0x00, + 0x00, 0x82, 0x9c, 0x00, 0x00, 0x02, 0x04, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6d, 0x69, + 0x73, 0x73, 0x20, 0x79, 0x6f, 0x75, 0x20, 0x46, 0x61, 0x77, 0x6b, 0x73, + }, }, } + for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { tamperTCP(tt.args.tcp, tt.args.field, tt.args.valueGen) @@ -132,7 +146,7 @@ func TestTamperTCPOptions(t *testing.T) { } } -func tcpTestPkt() *layers.TCP { +func testPkt() gopacket.Packet { tcpBytes := []byte{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x08, 0x00, 0x45, 0x00, 0x00, 0x34, 0x00, 0x00, 0x00, 0x00, 0x80, 0x06, 0xb9, 0x70, 0xc0, 0xa8, 0x00, 0x01, 0xc0, 0xa8, @@ -141,6 +155,5 @@ func tcpTestPkt() *layers.TCP { 0x73, 0x74, } - pkt := gopacket.NewPacket(tcpBytes, layers.LinkTypeEthernet, gopacket.Default) - return pkt.Layer(layers.LayerTypeTCP).(*layers.TCP) + return gopacket.NewPacket(tcpBytes, layers.LinkTypeEthernet, gopacket.Default) } From 16c7b8482f52c5ea35371ae87738975dc7c7bf74 Mon Sep 17 00:00:00 2001 From: garmr Date: Thu, 12 Oct 2023 12:43:13 -0700 Subject: [PATCH 4/8] moved checksum functions to common package, added disclaimer regarding lack of UDP support --- README.md | 7 ++ actions/action_test.go | 8 +- actions/fragment_packet.go | 108 ++++------------ actions/tamper_action.go | 224 +++++++++++++--------------------- actions/tamper_action_test.go | 4 + common/packet.go | 58 +++++++++ 6 files changed, 178 insertions(+), 231 deletions(-) create mode 100644 common/packet.go diff --git a/README.md b/README.md index 97a0af0..9e115e7 100644 --- a/README.md +++ b/README.md @@ -103,6 +103,13 @@ that "branching actions are not supported on inbound trees". Practically, this m actions can only be applied to outbound packets, while the sleep, drop, and tamper actions can apply to packets of either direction. (Note that this package does not currently enforce this; this is also a bug.) +## Disclaimer + +Currently only IPv4 and TCP are supported. There are plans to add support for UDP in the future (although pull requests +are welcome! Look at `TCPTamperAction` and `IPv4TamperAction` in `actions/tamper_action.go` as examples. +`UDPTamperAction` must implement the `actions.Action` interface). There are no plans at the moment to add support +for IPv6. + ## Credits See https://censorship.ai for more information about Geneva itself. diff --git a/actions/action_test.go b/actions/action_test.go index fdbe573..ac7a015 100644 --- a/actions/action_test.go +++ b/actions/action_test.go @@ -5,10 +5,12 @@ import ( "fmt" "testing" - "github.com/getlantern/geneva/actions" - "github.com/getlantern/geneva/internal/scanner" "github.com/google/gopacket" "github.com/google/gopacket/layers" + + "github.com/getlantern/geneva/actions" + "github.com/getlantern/geneva/common" + "github.com/getlantern/geneva/internal/scanner" ) var ( @@ -43,7 +45,7 @@ func TestIPv4HeaderChecksum(t *testing.T) { } expected := uint16(0xb861) - chksum := actions.ComputeIPv4Checksum(header) + chksum := common.CalculateIPv4Checksum(header) if chksum != expected { t.Fatalf("expected %#04x, got %#04x", expected, chksum) } diff --git a/actions/fragment_packet.go b/actions/fragment_packet.go index 5c0f14c..f8cb34f 100644 --- a/actions/fragment_packet.go +++ b/actions/fragment_packet.go @@ -7,10 +7,12 @@ import ( "strconv" "strings" - "github.com/getlantern/geneva/internal" - "github.com/getlantern/geneva/internal/scanner" "github.com/google/gopacket" "github.com/google/gopacket/layers" + + "github.com/getlantern/geneva/common" + "github.com/getlantern/geneva/internal" + "github.com/getlantern/geneva/internal/scanner" ) // FragmentAction is a Geneva action that splits a packet into two fragments and applies separate @@ -138,19 +140,12 @@ func fragmentTCPSegment(packet gopacket.Packet, fragSize int) ([]gopacket.Packet ipv4Buf := buf[ofs:] - // fix up the IP header's Total Length field and checksum + // fix up the IP header's Total Length field binary.BigEndian.PutUint16(ipv4Buf[2:], uint16(f1Len-ofs)) ipHdrLen := uint16(ipv4Buf[0]&0x0f) * 4 - ComputeIPv4Checksum(ipv4Buf[:ipHdrLen]) - - chksum := ComputeTCPChecksum( - ipv4Buf[:ipHdrLen], - ipv4Buf[ipHdrLen:headersLen-ofs], - ipv4Buf[headersLen-ofs:], - ) - binary.BigEndian.PutUint16(ipv4Buf[ipHdrLen+16:], chksum) first := gopacket.NewPacket(buf, packet.Layers()[0].LayerType(), gopacket.NoCopy) + updateChecksums(first) // create the second fragment. f2Len := headersLen + tcpPayloadLen - fragSize @@ -160,9 +155,8 @@ func fragmentTCPSegment(packet gopacket.Packet, fragSize int) ([]gopacket.Packet ipv4Buf = buf[ofs:] - // fix up the IP header's Total Length field and checksum + // fix up the IP header's Total Length field binary.BigEndian.PutUint16(ipv4Buf[2:], uint16(f2Len-ofs)) - ComputeIPv4Checksum(ipv4Buf[:ipHdrLen]) // Fix up the TCP sequence number. // Excitingly, Go does integer wrapping, so we don't have to. @@ -171,15 +165,8 @@ func fragmentTCPSegment(packet gopacket.Packet, fragSize int) ([]gopacket.Packet seqNum += uint32(fragSize) binary.BigEndian.PutUint32(tcp[4:], seqNum) - // fix up the TCP checksum - chksum = ComputeTCPChecksum( - ipv4Buf[:ipHdrLen], - ipv4Buf[ipHdrLen:headersLen-ofs], - ipv4Buf[headersLen-ofs:], - ) - binary.BigEndian.PutUint16(ipv4Buf[ipHdrLen+16:], chksum) - second := gopacket.NewPacket(buf, packet.Layers()[0].LayerType(), gopacket.NoCopy) + updateChecksums(second) return []gopacket.Packet{first, second}, nil } @@ -241,12 +228,13 @@ func FragmentIPPacket(packet gopacket.Packet, fragSize int) ([]gopacket.Packet, flagsAndFrags := (binary.BigEndian.Uint16(ipv4Buf[6:]) | 0x20) & 0xe0 binary.LittleEndian.PutUint16(ipv4Buf[6:], flagsAndFrags) - ComputeIPv4Checksum(ipv4Buf[:hdrLen]) - // slice off everything past the first fragment's end buf = buf[:uint16(ofs)+hdrLen+offset] first := gopacket.NewPacket(buf, packet.Layers()[0].LayerType(), gopacket.NoCopy) + if ipv4 := first.Layer(layers.LayerTypeIPv4).(*layers.IPv4); ipv4 != nil { + common.UpdateIPv4Checksum(ipv4) + } // Now start on the second fragment. // First copy the old IP header as-is, then copy just the second fragment's payload right @@ -264,84 +252,32 @@ func FragmentIPPacket(packet gopacket.Packet, fragSize int) ([]gopacket.Packet, flagsAndFrags = (binary.BigEndian.Uint16(ipv4Buf[6:]) & 0x40) + uint16(fragSize) binary.BigEndian.PutUint16(ipv4Buf[6:], flagsAndFrags) - ComputeIPv4Checksum(ipv4Buf[:hdrLen]) - second := gopacket.NewPacket(buf, packet.Layers()[0].LayerType(), gopacket.NoCopy) + if ipv4 := second.Layer(layers.LayerTypeIPv4).(*layers.IPv4); ipv4 != nil { + common.UpdateIPv4Checksum(ipv4) + } return []gopacket.Packet{first, second}, nil } -// VerifyIPv4Checksum verifies whether an IPv4 header's checksum field is correct. -func VerifyIPv4Checksum(header []byte) bool { - c := internal.OnesComplementChecksum{} - - for i := 0; i < len(header); i += 2 { - c.Add(binary.BigEndian.Uint16(header[i:])) +func updateChecksums(packet gopacket.Packet) { + if ipv4 := packet.Layer(layers.LayerTypeIPv4).(*layers.IPv4); ipv4 != nil { + common.UpdateIPv4Checksum(ipv4) } - return c.Finalize() == 0 + if tcp := packet.Layer(layers.LayerTypeTCP).(*layers.TCP); tcp != nil { + common.UpdateTCPChecksum(tcp) + } } -// ComputeIPv4Checksum computes a new checksum for the given IPv4 header and writes it into the -// header. -func ComputeIPv4Checksum(header []byte) uint16 { - // zero out the old checksum - binary.BigEndian.PutUint16(header[10:], 0) - +// VerifyIPv4Checksum verifies whether an IPv4 header's checksum field is correct. +func VerifyIPv4Checksum(header []byte) bool { c := internal.OnesComplementChecksum{} for i := 0; i < len(header); i += 2 { c.Add(binary.BigEndian.Uint16(header[i:])) } - chksum := c.Finalize() - binary.BigEndian.PutUint16(header[10:], chksum) - - return chksum -} - -// ComputeTCPChecksum computes the checksum field of a TCP header. -func ComputeTCPChecksum(ipHeader, tcpHeader, payload []byte) uint16 { - c := internal.OnesComplementChecksum{} - - // pseudo-header - c.Add(binary.BigEndian.Uint16(ipHeader[12:])) // source ip address - c.Add(binary.BigEndian.Uint16(ipHeader[14:])) // destination ip address - c.Add(uint16(ipHeader[6]) << 8) // protocol - tcpLength := binary.BigEndian.Uint16(ipHeader[2:]) - tcpLength -= uint16((ipHeader[0] & 0xf)) * 4 - c.Add(tcpLength) // "TCP Length" from RFC 793 - - // TCP header - for i := 0; i < len(tcpHeader); i += 2 { - if i == 16 { - // don't add existing checksum value - continue - } - - c.Add(binary.BigEndian.Uint16(tcpHeader[i:])) - } - - // TCP payload - for i := 0; i < len(payload); i += 2 { - if len(payload)-i == 1 { - // If there are an odd number of octets in the payload, the last octet must - // be padded on the right with zeros. - c.Add(uint16(payload[i]) << 8) - } else { - c.Add(binary.BigEndian.Uint16(payload[i:])) - } - } - - return c.Finalize() -} - -// VerifyTCPChecksum verifies whether a TCP header's checksum field is correct. -func VerifyTCPChecksum(ipHeader, tcpHeader, payload []byte) bool { - c := internal.OnesComplementChecksum{} - c.Add(ComputeTCPChecksum(ipHeader, tcpHeader, payload)) - c.Add(binary.BigEndian.Uint16(tcpHeader[16:])) - return c.Finalize() == 0 } diff --git a/actions/tamper_action.go b/actions/tamper_action.go index 0a9f847..8944256 100644 --- a/actions/tamper_action.go +++ b/actions/tamper_action.go @@ -2,6 +2,7 @@ package actions import ( "encoding/binary" + "errors" "fmt" "math/rand" "net" @@ -12,18 +13,15 @@ import ( "github.com/google/gopacket" "github.com/google/gopacket/layers" + "github.com/getlantern/geneva/common" "github.com/getlantern/geneva/internal/scanner" ) -// TODO: implement tamper actions for UDP - const ( // TamperReplace replaces the value of a packet field with the given value. TamperReplace = iota // TamperCorrupt replaces the value of a packet field with a randomly-generated value. TamperCorrupt - // TamperAdd adds the value to a packet field. - TamperAdd ) // TamperMode describes the way that the "tamper" action can manipulate a packet. @@ -36,14 +34,22 @@ func (tm TamperMode) String() string { return "replace" case TamperCorrupt: return "corrupt" - case TamperAdd: - return "add" } return "" } -// TamperAction is a Geneva action that modifies packets (typically values in the packet header). +// TamperAction is a Geneva action that modifies a given field of a packet while always +// trying to keep the packet valid. This is done by updating the checksums and lengths +// unless the tamper rule is specifically for the checksum or length. If proto is TCP +// and the field is an option, the option will be added if it doesn't exist. +// +// There are two modes for tampering: +// +// "replace" - replace the field with the given value. +// "corrupt" - replace the field with a randomly-generated value of the same bitsize. +// +// Currently, only TCP and IPv4 is supported. UDP support is planned for the future. type TamperAction struct { // Proto is the protocol layer where the modification will occur. Proto string @@ -145,7 +151,7 @@ func ParseTamperAction(s *scanner.Scanner) (Action, error) { case "TCP": return NewTCPTamperAction(tamperAction) case "UDP": - return NewUDPTamperAction(tamperAction) + return nil, fmt.Errorf("UDP tamper action not currently supported") default: return nil, fmt.Errorf("invalid tamper rule: %q is not a recognized protocol", proto) } @@ -155,26 +161,28 @@ func ParseTamperAction(s *scanner.Scanner) (Action, error) { // TCP Tamper Action // +// TCPField is a TCP field that can be modified by a TCPTamperAction. type TCPField uint8 const ( // supported TCP options. The other options are apparently obsolete and not used. - TCPOptionEol = layers.TCPOptionKindEndList // len 1 - TCPOptionNop = layers.TCPOptionKindNop // len 1 - TCPOptionMss = layers.TCPOptionKindMSS // len 4 - TCPOptionWscale = layers.TCPOptionKindWindowScale // len 3 - TCPOptionSackok = layers.TCPOptionKindSACKPermitted // len 2 - TCPOptionSack = layers.TCPOptionKindSACK // len 2+ - TCPOptionTimestamp = layers.TCPOptionKindTimestamps // len 10 - - // obsolete TCP options geneva uses and is in the strategies.txt file - TCPOptionAltCkhsum = 14 // len 3 - TCPOptionMd5Header = 19 // len 18 - TCPOptionUto = 28 // len 4 + TCPOptionEol = layers.TCPOptionKindEndList + TCPOptionNop = layers.TCPOptionKindNop + TCPOptionMss = layers.TCPOptionKindMSS + TCPOptionWscale = layers.TCPOptionKindWindowScale + TCPOptionSackok = layers.TCPOptionKindSACKPermitted + TCPOptionSack = layers.TCPOptionKindSACK + TCPOptionTimestamp = layers.TCPOptionKindTimestamps + + // obsolete TCP options geneva uses and is in the strategies.md document: + // https://github.com/Kkevsterrr/geneva/blob/master/strategies.md + TCPOptionAltCkhsum = 14 + TCPOptionMd5Header = 19 + TCPOptionUto = 28 // putting fields after options so that we can use the gopacket.TCPOptionKind constants for options. // this lets us use the same map for both fields and options and also directly compare - // tcpTamperAction.field == TCPOption when iterating over tcpPacket.Options + // tcpTamperAction.field == TCPOption when iterating over tcpPacket.Options. TCPFieldSrcPort = 9 TCPFieldDstPort = 10 TCPFieldSeq = 11 @@ -185,7 +193,7 @@ const ( TCPFieldUrgent = 17 TCPLoad = 18 - // TCP flag string representations for tamper rules + // TCP flag string representations for tamper rules. TCPFlagFin = "f" TCPFlagSyn = "s" TCPFlagRst = "r" @@ -257,6 +265,7 @@ func NewTCPTamperAction(ta TamperAction) (*TCPTamperAction, error) { switch ta.Mode { case TamperCorrupt: r := rand.New(rand.NewSource(time.Now().UnixNano())) + return &TCPTamperAction{ TamperAction: ta, field: field, @@ -264,6 +273,7 @@ func NewTCPTamperAction(ta TamperAction) (*TCPTamperAction, error) { }, nil case TamperReplace: gen := &tamperReplaceGen{} + switch { case field == TCPFieldFlags: gen.vUint = tcpFlagsToUint32(ta.NewValue) @@ -300,10 +310,11 @@ func NewTCPTamperAction(ta TamperAction) (*TCPTamperAction, error) { return nil, fmt.Errorf("invalid tamper rule: %q is not a valid tamper mode for TCP", ta.Mode) } +// Apply applies the tamper action to the given packet. func (a *TCPTamperAction) Apply(packet gopacket.Packet) ([]gopacket.Packet, error) { tcp := packet.Layer(layers.LayerTypeTCP).(*layers.TCP) if tcp == nil { - return nil, fmt.Errorf("packet does not have a TCP layer") + return nil, errors.New("packet does not have a TCP layer") } tamperTCP(tcp, a.field, a.valueGen) @@ -357,8 +368,7 @@ func tamperTCP(tcp *layers.TCP, field TCPField, valueGen tamperValueGen) { }) ol := len(tcp.Options) - tcp.Options[ol-2], tcp.Options[ol-1] = tcp.Options[ol-1], tcp.Options[ol-2] - opt = &tcp.Options[ol-2] + opt = &tcp.Options[ol-1] } opt.OptionData = valueGen.bytes(tcpOptionLengths[field]) @@ -367,7 +377,6 @@ func tamperTCP(tcp *layers.TCP, field TCPField, valueGen tamperValueGen) { } else { opt.OptionLength = uint8(tcpOptionLengths[field]) + 2 } - } // let gopacket handle converting the modified TCP headers into []byte for us since we changed the struct fields @@ -382,33 +391,34 @@ func tamperTCP(tcp *layers.TCP, field TCPField, valueGen tamperValueGen) { // tcpFlagsToUint32 converts a string of TCP flags to a uint32 bitmap. func tcpFlagsToUint32(flags string) uint32 { flags = strings.ToLower(flags) + var f uint32 for _, c := range flags { switch c { - case 'f': + case 'f': // FIN f |= 0x0001 - case 's': + case 's': // SYN f |= 0x0002 - case 'r': + case 'r': // RST f |= 0x0004 - case 'p': + case 'p': // PSH f |= 0x0008 - case 'a': + case 'a': // ACK f |= 0x0010 - case 'u': + case 'u': // URG f |= 0x0020 - case 'e': + case 'e': // ECE f |= 0x0040 - case 'c': + case 'c': // CWR f |= 0x0080 - case 'n': + case 'n': // NS f |= 0x0100 } } return f } -// setTCPFlags sets the tcp struct fields using flags as a bitmap, does not modify the raw packet bytes. +// setTCPFlags sets the tcp struct fields using flags bitmap, does not modify the raw packet bytes. func setTCPFlags(tcp *layers.TCP, flags uint16) { tcp.FIN = flags&0x0001 != 0 tcp.SYN = flags&0x0002 != 0 @@ -421,7 +431,8 @@ func setTCPFlags(tcp *layers.TCP, flags uint16) { tcp.NS = flags&0x0100 != 0 } -// updateTCPDataOffAndChksum updates the TCP data offset and checksum fields on the TCP struct and in the raw packet bytes. +// updateTCPDataOffAndChksum updates the TCP data offset and checksum fields on the TCP struct +// and in the raw packet bytes. func updateTCPDataOffAndChksum(tcp *layers.TCP) { // update data offset headerLen := len(tcp.Contents) @@ -429,22 +440,18 @@ func updateTCPDataOffAndChksum(tcp *layers.TCP) { tcp.Contents[12] = tcp.DataOffset << 4 // update checksum. - // the ComputeChecksum method requires the checksum bytes in the raw packet to be zeroed out. - tcp.Contents[16] = 0 - tcp.Contents[17] = 0 - chksum, _ := tcp.ComputeChecksum() - tcp.Checksum = chksum - binary.BigEndian.PutUint16(tcp.Contents[16:18], chksum) + common.UpdateTCPChecksum(tcp) } // // IPv4 Tamper Action // +// IPv4Field is an IPv4 field that can be modified by an IPv4TamperAction. type IPv4Field uint8 const ( - // ip field constants + // supported IPv4 fields IPv4FieldSrcIP = iota IPv4FieldDstIP IPv4FieldVersion @@ -460,27 +467,25 @@ const ( IPv4Load ) -var ( - ipv4Fields = map[string]IPv4Field{ - "srcip": IPv4FieldSrcIP, - "dstip": IPv4FieldDstIP, - "verion": IPv4FieldVersion, - "ihl": IPv4FieldIHL, - "tos": IPv4FieldTOS, - "length": IPv4FieldLength, - "id": IPv4FieldID, - // - // I don't know what the flags will look like in a tamper rule - // shouldn't be a problem since there isn't any tamper rules for flags currently - // "flags": IPv4FieldFlags, - // - "fragoffset": IPv4FieldFragOffset, - "ttl": IPv4FieldTTL, - "protocol": IPv4FieldProtocol, - "checksum": IPv4FieldChecksum, - "load": IPv4Load, - } -) +var ipv4Fields = map[string]IPv4Field{ + "srcip": IPv4FieldSrcIP, + "dstip": IPv4FieldDstIP, + "verion": IPv4FieldVersion, + "ihl": IPv4FieldIHL, + "tos": IPv4FieldTOS, + "length": IPv4FieldLength, + "id": IPv4FieldID, + // + // I don't know what the flags will look like in a tamper rule + // shouldn't be a problem since there isn't any tamper rules for IP flags currently + // "flags": IPv4FieldFlags, + // + "fragoffset": IPv4FieldFragOffset, + "ttl": IPv4FieldTTL, + "protocol": IPv4FieldProtocol, + "checksum": IPv4FieldChecksum, + "load": IPv4Load, +} // IPv4TamperAction is a Geneva action that modifies IPv4 packets. type IPv4TamperAction struct { @@ -502,6 +507,7 @@ func NewIPv4TamperAction(ta TamperAction) (*IPv4TamperAction, error) { switch ta.Mode { case TamperCorrupt: r := rand.New(rand.NewSource(time.Now().UnixNano())) + return &IPv4TamperAction{ TamperAction: ta, field: field, @@ -509,6 +515,7 @@ func NewIPv4TamperAction(ta TamperAction) (*IPv4TamperAction, error) { }, nil case TamperReplace: gen := &tamperReplaceGen{} + switch field { case IPv4FieldSrcIP, IPv4FieldDstIP: // parse IP address from NewValue and convert to []byte @@ -544,6 +551,7 @@ func NewIPv4TamperAction(ta TamperAction) (*IPv4TamperAction, error) { return nil, fmt.Errorf("invalid tamper rule: %q is not a valid tamper mode for IPv4", ta.Mode) } +// Apply applies the tamper action to the given packet. func (a *IPv4TamperAction) Apply(packet gopacket.Packet) ([]gopacket.Packet, error) { ip := packet.Layer(layers.LayerTypeIPv4).(*layers.IPv4) if ip == nil { @@ -551,6 +559,7 @@ func (a *IPv4TamperAction) Apply(packet gopacket.Packet) ([]gopacket.Packet, err } tamperIPv4(ip, a.field, a.valueGen) + common.UpdateIPv4Checksum(ip) return a.Action.Apply(packet) } @@ -591,95 +600,22 @@ func tamperIPv4(ip *layers.IPv4, field IPv4Field, valueGen tamperValueGen) error ip.Contents = make([]byte, len(sb.Bytes())) copy(ip.Contents, sb.Bytes()) - // do we update length and checksum??? - // it doesn't look like the geneva team does when they tamper with the IP layer - return nil } +// updateIPv4LengthAndChksum updates the IPv4 length func updateIPv4LengthAndChksum(ip *layers.IPv4) { length := len(ip.Contents) + len(ip.Payload) ip.Length = uint16(length) binary.BigEndian.PutUint16(ip.Contents[2:4], ip.Length) - buf := make([]byte, length) - copy(buf, ip.Contents) - copy(buf[len(ip.Contents):], ip.Payload) - chksum := checksum(buf) - ip.Checksum = chksum - binary.BigEndian.PutUint16(ip.Contents[10:12], chksum) } -// copied directly from gopacket/layers/ip4.go because they didn't export one. for whatever some reason.. -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) -} - -// Should we implement tamper for UDP?? - // // UDP Tamper Action +// TODO: implement UDP tamper actions // -type UDPField uint8 - -const ( -// udp field constants -) - -var ( - udpFields = map[string]UDPField{} -) - -// UDPTamperAction is a Geneva action that modifies UDP packets. -type UDPTamperAction struct { - // TamperAction is the underlying action parsed from the tamper rule. - TamperAction - // field is the UDP field to modify. - field UDPField - // valueGen is the value generator to use when modifying the field. - valueGen tamperValueGen -} - -// NewUDPTamperAction returns a new UDPTamperAction from the given TamperAction. -func NewUDPTamperAction(ta TamperAction) (*UDPTamperAction, error) { - return nil, fmt.Errorf("UDP tamper action unimplemented") -} - -func (a *UDPTamperAction) Apply(packet gopacket.Packet) ([]gopacket.Packet, error) { - udp := packet.Layer(layers.LayerTypeUDP).(*layers.UDP) - if udp == nil { - return nil, fmt.Errorf("packet does not have a UDP layer") - } - - tamperUDP(udp, a.field, a.valueGen) - - return a.Action.Apply(packet) -} - -// tamperUDP modifies the given UDP field using the given value generator. -func tamperUDP(tcp *layers.UDP, field UDPField, valueGen tamperValueGen) error { - return fmt.Errorf("tamper UDP not implemented") -} - +// tamperValueGen is a value generator for tamper actions. type tamperValueGen interface { uint(bitSize int) uint32 bytes(n int) []byte @@ -692,10 +628,12 @@ type tamperReplaceGen struct { vBytes []byte } -func (g *tamperReplaceGen) uint(bitSize int) uint32 { +// uint returns the uint value. bitSize is ignored. +func (g *tamperReplaceGen) uint(_ int) uint32 { return g.vUint } +// bytes returns the byte slice or an empty byte slice if n == 0. func (g *tamperReplaceGen) bytes(n int) []byte { if n == 0 { return []byte{} @@ -708,6 +646,7 @@ type tamperCorruptGen struct { r *rand.Rand } +// uint returns a random value of the given bit size as a uint32. func (g *tamperCorruptGen) uint(bitSize int) uint32 { n := g.r.Intn(1< 20 { n = g.r.Intn(n) } + b := make([]byte, n) g.r.Read(b) return b diff --git a/actions/tamper_action_test.go b/actions/tamper_action_test.go index f479d3a..5acc565 100644 --- a/actions/tamper_action_test.go +++ b/actions/tamper_action_test.go @@ -13,6 +13,8 @@ import ( ) func TestParseTamperAction(t *testing.T) { + t.Parallel() + tests := []struct { name string rule string @@ -82,6 +84,8 @@ func TestParseTamperAction(t *testing.T) { } func TestTamperTCP(t *testing.T) { + t.Parallel() + type args struct { tcp *layers.TCP field TCPField diff --git a/common/packet.go b/common/packet.go new file mode 100644 index 0000000..2f3d7d7 --- /dev/null +++ b/common/packet.go @@ -0,0 +1,58 @@ +package common + +import ( + "encoding/binary" + + "github.com/google/gopacket/layers" +) + +// UpdateTCPChecksum updates the TCP checksum field and the raw bytes for a gopacket TCP layer. +func UpdateTCPChecksum(tcp *layers.TCP) error { + // the ComputeChecksum method requires the checksum bytes in the raw packet to be zeroed out. + tcp.Contents[16] = 0 + tcp.Contents[17] = 0 + + chksum, err := tcp.ComputeChecksum() + if err != nil { + return err + } + + tcp.Checksum = chksum + binary.BigEndian.PutUint16(tcp.Contents[16:18], chksum) + + return nil +} + +// UpdateIPv4Checksum updates the IPv4 checksum field and the raw bytes for a gopacket IPv4 layer. +func UpdateIPv4Checksum(ip *layers.IPv4) error { + buf := make([]byte, ip.Length) + copy(buf, ip.Contents) + copy(buf[len(ip.Contents):], ip.Payload) + + chksum := CalculateIPv4Checksum(buf) + ip.Checksum = chksum + binary.BigEndian.PutUint16(ip.Contents[10:12], chksum) + + return nil +} + +// copied directly from gopacket/layers/ip4.go because they didn't export one. for whatever some reason.. +func CalculateIPv4Checksum(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 csum > 0xFFFF { + // Add carry to the sum + csum = (csum >> 16) + uint32(uint16(csum)) + } + // Flip all the bits + return ^uint16(csum) +} From 9257f7356fe87ea5bf45e1ecc324bc860ff5b121 Mon Sep 17 00:00:00 2001 From: garmr Date: Thu, 12 Oct 2023 16:30:16 -0700 Subject: [PATCH 5/8] fixed IPv4 checksum calculation --- actions/tamper_action_test.go | 6 +++++- common/packet.go | 21 ++++++++++----------- 2 files changed, 15 insertions(+), 12 deletions(-) diff --git a/actions/tamper_action_test.go b/actions/tamper_action_test.go index 5acc565..4690d85 100644 --- a/actions/tamper_action_test.go +++ b/actions/tamper_action_test.go @@ -69,7 +69,9 @@ func TestParseTamperAction(t *testing.T) { }, } for _, tt := range tests { + tt := tt t.Run(tt.name, func(t *testing.T) { + t.Parallel() s := scanner.NewScanner(tt.rule) got, err := ParseTamperAction(s) if (err != nil) != tt.wantErr { @@ -117,7 +119,7 @@ func TestTamperTCP(t *testing.T) { }, want: []byte{ 0x30, 0x39, 0xd4, 0x31, 0xde, 0xad, 0xbe, 0xef, 0x00, 0x00, 0x00, 0x00, 0x70, 0x02, 0x00, - 0x00, 0x82, 0x9c, 0x00, 0x00, 0x02, 0x04, 0x20, 0x00, 0x0e, 0x05, 0xff, 0xff, 0xff, 0x00, + 0x00, 0x82, 0x9c, 0x00, 0x00, 0x02, 0x04, 0x20, 0x00, 0x00, 0x0e, 0x05, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x54, 0x65, 0x73, 0x74, }, }, { @@ -140,7 +142,9 @@ func TestTamperTCP(t *testing.T) { } for _, tt := range tests { + tt := tt t.Run(tt.name, func(t *testing.T) { + t.Parallel() tamperTCP(tt.args.tcp, tt.args.field, tt.args.valueGen) got := append([]byte{}, tt.args.tcp.Contents...) diff --git a/common/packet.go b/common/packet.go index 2f3d7d7..0539baf 100644 --- a/common/packet.go +++ b/common/packet.go @@ -25,28 +25,27 @@ func UpdateTCPChecksum(tcp *layers.TCP) error { // UpdateIPv4Checksum updates the IPv4 checksum field and the raw bytes for a gopacket IPv4 layer. func UpdateIPv4Checksum(ip *layers.IPv4) error { - buf := make([]byte, ip.Length) - copy(buf, ip.Contents) - copy(buf[len(ip.Contents):], ip.Payload) - - chksum := CalculateIPv4Checksum(buf) + chksum := CalculateIPv4Checksum(ip.Contents) ip.Checksum = chksum binary.BigEndian.PutUint16(ip.Contents[10:12], chksum) return nil } -// copied directly from gopacket/layers/ip4.go because they didn't export one. for whatever some reason.. +// copied from gopacket/layers/ip4.go because they didn't export one. for whatever some reason.. func CalculateIPv4Checksum(bytes []byte) uint16 { + buf := make([]byte, len(bytes)) + copy(buf, bytes) + // Clear checksum bytes - bytes[10] = 0 - bytes[11] = 0 + buf[10] = 0 + buf[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 i := 0; i < len(buf); i += 2 { + csum += uint32(buf[i]) << 8 + csum += uint32(buf[i+1]) } for csum > 0xFFFF { From 0e4adba593404768ce9017b19c6f0fa193c2689e Mon Sep 17 00:00:00 2001 From: garmr Date: Fri, 13 Oct 2023 16:27:52 -0700 Subject: [PATCH 6/8] fix newstrategy test in geneva_test.go, fix linter errors --- actions/actions.go | 8 +- actions/duplicate_action.go | 7 +- actions/fragment_packet.go | 8 +- actions/tamper_action.go | 155 ++++++++++++++++++++-------------- actions/tamper_action_test.go | 2 + common/packet.go | 14 +-- geneva_test.go | 4 +- 7 files changed, 119 insertions(+), 79 deletions(-) diff --git a/actions/actions.go b/actions/actions.go index 4e9edcb..01bd3d4 100644 --- a/actions/actions.go +++ b/actions/actions.go @@ -4,17 +4,21 @@ package actions import ( + "errors" "fmt" + "github.com/google/gopacket" + "github.com/getlantern/geneva/internal" "github.com/getlantern/geneva/internal/scanner" "github.com/getlantern/geneva/triggers" - "github.com/google/gopacket" // gopacket best practice says import this, too. _ "github.com/google/gopacket/layers" ) +var ErrInvalidAction = errors.New("invalid action") + // ActionTree represents a Geneva (trigger, action) pair. // // Technically, Geneva uses the term "action tree" to refer to the tree of actions in the tuple @@ -122,5 +126,5 @@ func ParseAction(s *scanner.Scanner) (Action, error) { return DefaultSendAction, nil } - return nil, fmt.Errorf("invalid action at %d", s.Pos()) + return nil, fmt.Errorf("%w at %d", ErrInvalidAction, s.Pos()) } diff --git a/actions/duplicate_action.go b/actions/duplicate_action.go index 490f7ed..7512144 100644 --- a/actions/duplicate_action.go +++ b/actions/duplicate_action.go @@ -4,9 +4,10 @@ import ( "errors" "fmt" + "github.com/google/gopacket" + "github.com/getlantern/geneva/internal" "github.com/getlantern/geneva/internal/scanner" - "github.com/google/gopacket" ) // DuplicateAction is a Geneva action that duplicates a packet and applies separate action trees to @@ -114,6 +115,10 @@ func ParseDuplicateAction(s *scanner.Scanner) (Action, error) { } if action.Left, err = ParseAction(s); err != nil { + if !errors.Is(err, ErrInvalidAction) { + return nil, err + } + if c, err2 := s.Peek(); err2 == nil && c == ',' { action.Left = &SendAction{} } else { diff --git a/actions/fragment_packet.go b/actions/fragment_packet.go index f8cb34f..0124cec 100644 --- a/actions/fragment_packet.go +++ b/actions/fragment_packet.go @@ -261,11 +261,11 @@ func FragmentIPPacket(packet gopacket.Packet, fragSize int) ([]gopacket.Packet, } func updateChecksums(packet gopacket.Packet) { - if ipv4 := packet.Layer(layers.LayerTypeIPv4).(*layers.IPv4); ipv4 != nil { + if ipv4, _ := packet.Layer(layers.LayerTypeIPv4).(*layers.IPv4); ipv4 != nil { common.UpdateIPv4Checksum(ipv4) } - if tcp := packet.Layer(layers.LayerTypeTCP).(*layers.TCP); tcp != nil { + if tcp, _ := packet.Layer(layers.LayerTypeTCP).(*layers.TCP); tcp != nil { common.UpdateTCPChecksum(tcp) } } @@ -375,6 +375,10 @@ func ParseFragmentAction(s *scanner.Scanner) (Action, error) { } if action.FirstFragmentAction, err = ParseAction(s); err != nil { + if !errors.Is(err, ErrInvalidAction) { + return nil, err + } + if c, err2 := s.Peek(); err2 == nil && c == ',' { action.FirstFragmentAction = &SendAction{} } else { diff --git a/actions/tamper_action.go b/actions/tamper_action.go index 8944256..bfff1af 100644 --- a/actions/tamper_action.go +++ b/actions/tamper_action.go @@ -1,3 +1,6 @@ +// Package actions describes the actions that can be applied to a given packet. +// +//nolint:depguard,dupword package actions import ( @@ -24,6 +27,13 @@ const ( TamperCorrupt ) +//nolint:revive +var ( + ErrInvalidTamperMode = errors.New("invalid tamper mode") + ErrInvalidTamperRule = errors.New("invalid tamper rule") + ErrUDPNotSupported = errors.New("UDP tamper action not currently supported") +) + // TamperMode describes the way that the "tamper" action can manipulate a packet. type TamperMode int @@ -80,19 +90,19 @@ func (a *TamperAction) String() string { // If the string is malformed, an error will be returned instead. func ParseTamperAction(s *scanner.Scanner) (Action, error) { if _, err := s.Expect("tamper{"); err != nil { - return nil, fmt.Errorf("invalid tamper rule: %w", err) + return nil, fmt.Errorf("%s: %w", ErrInvalidTamperRule, err) } str, err := s.Until('}') if err != nil { - return nil, fmt.Errorf("invalid tamper rule: %w", err) + return nil, fmt.Errorf("%s: %w", ErrInvalidTamperRule, err) } _, _ = s.Pop() fields := strings.Split(str, ":") if len(fields) < 3 || len(fields) > 4 { - return nil, fmt.Errorf("invalid fields for tamper rule: %s", str) + return nil, fmt.Errorf("%s: invalid field, %w", ErrInvalidTamperRule, err) } var ( @@ -111,7 +121,8 @@ func ParseTamperAction(s *scanner.Scanner) (Action, error) { mode = TamperCorrupt default: return nil, fmt.Errorf( - "invalid tamper mode: %q must be either 'replace' or 'corrupt'", + "%w: %q must be either 'replace' or 'corrupt'", + ErrInvalidTamperMode, fields[2], ) } @@ -123,37 +134,46 @@ func ParseTamperAction(s *scanner.Scanner) (Action, error) { NewValue: newValue, } - if _, err := s.Expect("("); err == nil { - if tamperAction.Action, err = ParseAction(s); err != nil { - if c, err2 := s.Peek(); err2 == nil && c == ')' { - tamperAction.Action = &SendAction{} - } else { - return nil, fmt.Errorf("invalid action for tamper rule: %w", err) - } + if _, err = s.Expect("("); err != nil { + tamperAction.Action = &SendAction{} + return newTamperAction(tamperAction) + } + + if tamperAction.Action, err = ParseAction(s); err != nil { + if !errors.Is(err, ErrInvalidAction) { + return nil, err } - if _, err = s.Expect(","); err == nil { - if !s.FindToken(")", true) { - return nil, fmt.Errorf("tamper rules can only have one action") - } + if c, err2 := s.Peek(); err2 == nil && c == ')' { + tamperAction.Action = &SendAction{} + } else { + return nil, fmt.Errorf("%s: invalid action, %w", ErrInvalidTamperRule, err) } + } - if _, err := s.Expect(")"); err != nil { - return nil, fmt.Errorf("unexpected token in tamper rule: %w", err) + if _, err = s.Expect(","); err == nil { + if !s.FindToken(")", true) { + return nil, fmt.Errorf("%w: only one action is allowed", ErrInvalidTamperRule) } - } else { - tamperAction.Action = &SendAction{} } - switch proto { + if _, err = s.Expect(")"); err != nil { + return nil, fmt.Errorf("%s: unexpected token: %w", ErrInvalidTamperRule, err) + } + + return newTamperAction(tamperAction) +} + +func newTamperAction(ta TamperAction) (Action, error) { + switch ta.Proto { case "IP": - return NewIPv4TamperAction(tamperAction) + return NewIPv4TamperAction(ta) case "TCP": - return NewTCPTamperAction(tamperAction) + return NewTCPTamperAction(ta) case "UDP": - return nil, fmt.Errorf("UDP tamper action not currently supported") + return nil, ErrUDPNotSupported default: - return nil, fmt.Errorf("invalid tamper rule: %q is not a recognized protocol", proto) + return nil, fmt.Errorf("%w: %q is not a recognized protocol", ErrInvalidTamperRule, ta.Proto) } } @@ -164,6 +184,7 @@ func ParseTamperAction(s *scanner.Scanner) (Action, error) { // TCPField is a TCP field that can be modified by a TCPTamperAction. type TCPField uint8 +//nolint:revive const ( // supported TCP options. The other options are apparently obsolete and not used. TCPOptionEol = layers.TCPOptionKindEndList @@ -183,15 +204,16 @@ const ( // putting fields after options so that we can use the gopacket.TCPOptionKind constants for options. // this lets us use the same map for both fields and options and also directly compare // tcpTamperAction.field == TCPOption when iterating over tcpPacket.Options. - TCPFieldSrcPort = 9 - TCPFieldDstPort = 10 - TCPFieldSeq = 11 - TCPFieldAck = 12 - TCPFieldDataOff = 13 - TCPFieldFlags = 15 - TCPFieldWindow = 16 - TCPFieldUrgent = 17 - TCPLoad = 18 + TCPFieldSrcPort = 9 + TCPFieldDstPort = 10 + TCPFieldSeq = 11 + TCPFieldAck = 12 + TCPFieldDataOff = 13 + TCPFieldFlags = 15 + TCPFieldWindow = 16 + TCPFieldUrgent = 17 + TCPFieldChecksum = 18 + TCPLoad = 20 // TCP flag string representations for tamper rules. TCPFlagFin = "f" @@ -217,6 +239,7 @@ var ( "flags": TCPFieldFlags, "window": TCPFieldWindow, "urgent": TCPFieldUrgent, + "chksum": TCPFieldChecksum, "options-eol": TCPOptionEol, "options-nop": TCPOptionNop, "options-mss": TCPOptionMss, @@ -259,12 +282,12 @@ type TCPTamperAction struct { func NewTCPTamperAction(ta TamperAction) (*TCPTamperAction, error) { field, ok := tcpFields[ta.Field] if !ok { - return nil, fmt.Errorf("invalid tamper rule: %q is not a recognized TCP field", ta.Field) + return nil, fmt.Errorf("%w: %q is not a recognized TCP field", ErrInvalidTamperRule, ta.Field) } switch ta.Mode { case TamperCorrupt: - r := rand.New(rand.NewSource(time.Now().UnixNano())) + r := rand.New(rand.NewSource(time.Now().UnixNano())) //nolint:gosec return &TCPTamperAction{ TamperAction: ta, @@ -294,7 +317,12 @@ func NewTCPTamperAction(ta TamperAction) (*TCPTamperAction, error) { default: val, err := strconv.ParseUint(ta.NewValue, 10, 32) if err != nil { - return nil, fmt.Errorf("invalid tamper rule: %q is not a valid value for field %q", ta.NewValue, ta.Field) + return nil, fmt.Errorf( + "%w: %q is not a valid value for field %q", + ErrInvalidTamperRule, + ta.NewValue, + ta.Field, + ) } gen.vUint = uint32(val) @@ -307,14 +335,14 @@ func NewTCPTamperAction(ta TamperAction) (*TCPTamperAction, error) { }, nil } - return nil, fmt.Errorf("invalid tamper rule: %q is not a valid tamper mode for TCP", ta.Mode) + return nil, fmt.Errorf("%w: %q is not a valid tamper mode for TCP", ErrInvalidTamperRule, ta.Mode) } // Apply applies the tamper action to the given packet. func (a *TCPTamperAction) Apply(packet gopacket.Packet) ([]gopacket.Packet, error) { tcp := packet.Layer(layers.LayerTypeTCP).(*layers.TCP) if tcp == nil { - return nil, errors.New("packet does not have a TCP layer") + return nil, errors.New("packet does not have a TCP layer") //nolint:goerr113 } tamperTCP(tcp, a.field, a.valueGen) @@ -322,12 +350,13 @@ func (a *TCPTamperAction) Apply(packet gopacket.Packet) ([]gopacket.Packet, erro // if tampering with TCP options, we need to update the data offset and checksum if strings.HasPrefix(a.Field, "options") { updateTCPDataOffAndChksum(tcp) + if ip := packet.Layer(layers.LayerTypeIPv4).(*layers.IPv4); ip != nil { updateIPv4LengthAndChksum(ip) } } - return a.Action.Apply(packet) + return a.Action.Apply(packet) //nolint:wrapcheck } // tamperTCP modifies the given TCP field using the given value generator. @@ -347,6 +376,8 @@ func tamperTCP(tcp *layers.TCP, field TCPField, valueGen tamperValueGen) { tcp.Window = uint16(valueGen.uint(16)) case TCPFieldUrgent: tcp.Urgent = uint16(valueGen.uint(16)) + case TCPFieldChecksum: + tcp.Checksum = uint16(valueGen.uint(16)) case TCPFieldFlags: setTCPFlags(tcp, uint16(valueGen.uint(16))) case TCPLoad: @@ -354,6 +385,7 @@ func tamperTCP(tcp *layers.TCP, field TCPField, valueGen tamperValueGen) { default: // find option in TCP header var opt *layers.TCPOption + for i, o := range tcp.Options { if field == TCPField(o.OptionType) { opt = &tcp.Options[i] @@ -383,7 +415,7 @@ func tamperTCP(tcp *layers.TCP, field TCPField, valueGen tamperValueGen) { // instead of the underlying []byte directly. SerializeTo doesn't write the changes to the raw packet // so we have to copy the formatted bytes back into the packet header. sb := gopacket.NewSerializeBuffer() - tcp.SerializeTo(sb, gopacket.SerializeOptions{}) + tcp.SerializeTo(sb, gopacket.SerializeOptions{}) //nolint:errcheck,gosec tcp.Contents = make([]byte, len(sb.Bytes())) copy(tcp.Contents, sb.Bytes()) } @@ -393,7 +425,7 @@ func tcpFlagsToUint32(flags string) uint32 { flags = strings.ToLower(flags) var f uint32 - for _, c := range flags { + for _, c := range flags { //nolint:wsl switch c { case 'f': // FIN f |= 0x0001 @@ -415,6 +447,7 @@ func tcpFlagsToUint32(flags string) uint32 { f |= 0x0100 } } + return f } @@ -450,8 +483,9 @@ func updateTCPDataOffAndChksum(tcp *layers.TCP) { // IPv4Field is an IPv4 field that can be modified by an IPv4TamperAction. type IPv4Field uint8 +//nolint:revive const ( - // supported IPv4 fields + // supported IPv4 fields. IPv4FieldSrcIP = iota IPv4FieldDstIP IPv4FieldVersion @@ -473,7 +507,7 @@ var ipv4Fields = map[string]IPv4Field{ "verion": IPv4FieldVersion, "ihl": IPv4FieldIHL, "tos": IPv4FieldTOS, - "length": IPv4FieldLength, + "len": IPv4FieldLength, "id": IPv4FieldID, // // I don't know what the flags will look like in a tamper rule @@ -501,12 +535,12 @@ type IPv4TamperAction struct { func NewIPv4TamperAction(ta TamperAction) (*IPv4TamperAction, error) { field, ok := ipv4Fields[ta.Field] if !ok { - return nil, fmt.Errorf("invalid tamper rule: %q is not a recognized IPv4 field", ta.Field) + return nil, fmt.Errorf("%w: %q is not a recognized IPv4 field", ErrInvalidTamperRule, ta.Field) } switch ta.Mode { case TamperCorrupt: - r := rand.New(rand.NewSource(time.Now().UnixNano())) + r := rand.New(rand.NewSource(time.Now().UnixNano())) //nolint:gosec return &IPv4TamperAction{ TamperAction: ta, @@ -521,11 +555,11 @@ func NewIPv4TamperAction(ta TamperAction) (*IPv4TamperAction, error) { // parse IP address from NewValue and convert to []byte ip := net.ParseIP(ta.NewValue) if ip == nil { - return nil, fmt.Errorf("invalid tamper rule: %q is not a valid IPv4 address", ta.NewValue) + return nil, fmt.Errorf("%w: %q is not a valid IPv4 address", ErrInvalidTamperRule, ta.NewValue) } if ip.To4() == nil { - return nil, fmt.Errorf("invalid tamper rule: IPv6 is not supported") + return nil, fmt.Errorf("%w: IPv6 is not supported", ErrInvalidTamperRule) } gen.vBytes = ip @@ -535,7 +569,7 @@ func NewIPv4TamperAction(ta TamperAction) (*IPv4TamperAction, error) { // parse uint from NewValue val, err := strconv.ParseUint(ta.NewValue, 10, 32) if err != nil { - return nil, fmt.Errorf("invalid tamper rule: %q is not a valid value for field %q", ta.NewValue, ta.Field) + return nil, fmt.Errorf("%w: %q is not a valid value for field %q", ErrInvalidTamperRule, ta.NewValue, ta.Field) } gen.vUint = uint32(val) @@ -548,24 +582,24 @@ func NewIPv4TamperAction(ta TamperAction) (*IPv4TamperAction, error) { }, nil } - return nil, fmt.Errorf("invalid tamper rule: %q is not a valid tamper mode for IPv4", ta.Mode) + return nil, fmt.Errorf("%w: %q is not a valid tamper mode for IPv4", ErrInvalidTamperRule, ta.Mode) } // Apply applies the tamper action to the given packet. func (a *IPv4TamperAction) Apply(packet gopacket.Packet) ([]gopacket.Packet, error) { ip := packet.Layer(layers.LayerTypeIPv4).(*layers.IPv4) if ip == nil { - return nil, fmt.Errorf("packet does not have a IPv4 layer") + return nil, errors.New("packet does not have a IPv4 layer") //nolint:goerr113 } tamperIPv4(ip, a.field, a.valueGen) common.UpdateIPv4Checksum(ip) - return a.Action.Apply(packet) + return a.Action.Apply(packet) //nolint:wrapcheck } // tamperIPv4 modifies the given IP field using the given value generator. -func tamperIPv4(ip *layers.IPv4, field IPv4Field, valueGen tamperValueGen) error { +func tamperIPv4(ip *layers.IPv4, field IPv4Field, valueGen tamperValueGen) { switch field { case IPv4FieldSrcIP: ip.SrcIP = valueGen.bytes(4) @@ -580,7 +614,7 @@ func tamperIPv4(ip *layers.IPv4, field IPv4Field, valueGen tamperValueGen) error case IPv4FieldLength: ip.Length = uint16(valueGen.uint(16)) case IPv4FieldFlags: - // TODO: maybe implement this? + // not implemented yet. see comment above. case IPv4FieldFragOffset: ip.FragOffset = uint16(valueGen.uint(16)) case IPv4FieldTTL: @@ -596,25 +630,18 @@ func tamperIPv4(ip *layers.IPv4, field IPv4Field, valueGen tamperValueGen) error // let gopacket handle converting modified packet into []byte again, it's just easier // again copy the bytes back into the packet header sb := gopacket.NewSerializeBuffer() - ip.SerializeTo(sb, gopacket.SerializeOptions{}) + ip.SerializeTo(sb, gopacket.SerializeOptions{}) //nolint:errcheck,gosec ip.Contents = make([]byte, len(sb.Bytes())) copy(ip.Contents, sb.Bytes()) - - return nil } -// updateIPv4LengthAndChksum updates the IPv4 length +// updateIPv4LengthAndChksum updates the IPv4 length. func updateIPv4LengthAndChksum(ip *layers.IPv4) { length := len(ip.Contents) + len(ip.Payload) ip.Length = uint16(length) binary.BigEndian.PutUint16(ip.Contents[2:4], ip.Length) } -// -// UDP Tamper Action -// TODO: implement UDP tamper actions -// - // tamperValueGen is a value generator for tamper actions. type tamperValueGen interface { uint(bitSize int) uint32 @@ -638,6 +665,7 @@ func (g *tamperReplaceGen) bytes(n int) []byte { if n == 0 { return []byte{} } + return append([]byte{}, g.vBytes...) } @@ -654,6 +682,8 @@ func (g *tamperCorruptGen) uint(bitSize int) uint32 { // bytes returns a random byte slice of length n if n <= 20, otherwise it returns a // a random byte slice of random length up to n. +// +//nolint:gosec func (g *tamperCorruptGen) bytes(n int) []byte { if n > 20 { n = g.r.Intn(n) @@ -661,5 +691,6 @@ func (g *tamperCorruptGen) bytes(n int) []byte { b := make([]byte, n) g.r.Read(b) + return b } diff --git a/actions/tamper_action_test.go b/actions/tamper_action_test.go index 4690d85..fabbb41 100644 --- a/actions/tamper_action_test.go +++ b/actions/tamper_action_test.go @@ -1,3 +1,4 @@ +//nolint:depguard,testpackage package actions import ( @@ -93,6 +94,7 @@ func TestTamperTCP(t *testing.T) { field TCPField valueGen tamperValueGen } + tests := []struct { name string args args diff --git a/common/packet.go b/common/packet.go index 0539baf..510dd96 100644 --- a/common/packet.go +++ b/common/packet.go @@ -7,31 +7,25 @@ import ( ) // UpdateTCPChecksum updates the TCP checksum field and the raw bytes for a gopacket TCP layer. -func UpdateTCPChecksum(tcp *layers.TCP) error { +func UpdateTCPChecksum(tcp *layers.TCP) { // the ComputeChecksum method requires the checksum bytes in the raw packet to be zeroed out. tcp.Contents[16] = 0 tcp.Contents[17] = 0 - chksum, err := tcp.ComputeChecksum() - if err != nil { - return err - } + chksum, _ := tcp.ComputeChecksum() tcp.Checksum = chksum binary.BigEndian.PutUint16(tcp.Contents[16:18], chksum) - - return nil } // UpdateIPv4Checksum updates the IPv4 checksum field and the raw bytes for a gopacket IPv4 layer. -func UpdateIPv4Checksum(ip *layers.IPv4) error { +func UpdateIPv4Checksum(ip *layers.IPv4) { chksum := CalculateIPv4Checksum(ip.Contents) ip.Checksum = chksum binary.BigEndian.PutUint16(ip.Contents[10:12], chksum) - - return nil } +// CalculateIPv4Checksum calculates the IPv4 checksum for the given bytes. // copied from gopacket/layers/ip4.go because they didn't export one. for whatever some reason.. func CalculateIPv4Checksum(bytes []byte) uint16 { buf := make([]byte, len(bytes)) diff --git a/geneva_test.go b/geneva_test.go index ee4b3d8..3d7f070 100644 --- a/geneva_test.go +++ b/geneva_test.go @@ -36,10 +36,10 @@ var examples = []string{ func TestNewStrategy(t *testing.T) { t.Parallel() - for _, s := range examples { + for i, s := range examples { _, err := geneva.NewStrategy(s) if err != nil { - t.Errorf("failed to parse strategy: %v", err) + t.Errorf("failed to parse strategy %d %q: %v", i, s, err) } } } From 249771455584fd664aa457b605ee3a3bc7cf1cfa Mon Sep 17 00:00:00 2001 From: garmr Date: Sat, 14 Oct 2023 16:29:03 -0700 Subject: [PATCH 7/8] toned down linter rules a bit and fixed remaining linter issues --- .golangci.yml | 28 ++++++++++++++++++++++++ actions/duplicate_action.go | 8 +++---- actions/fragment_packet.go | 9 ++++---- actions/tamper_action.go | 41 +++++++++++++++-------------------- actions/tamper_action_test.go | 2 +- common/packet.go | 3 ++- geneva.go | 32 +++++++++++++-------------- internal/internal.go | 1 + internal/scanner/scanner.go | 9 ++++---- strategy/strategy.go | 18 +++++++-------- 10 files changed, 88 insertions(+), 63 deletions(-) diff --git a/.golangci.yml b/.golangci.yml index 05d2320..ac4e65e 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -19,6 +19,10 @@ linters: - ireturn - nolintlint - varnamelen + - depguard + - dupword + - testpackage + - godox linters-settings: misspell: @@ -28,6 +32,23 @@ linters-settings: block-size: 2 exhaustive: default-signifies-exhaustive: true + gosec: + excludes: + - G104 # Duplicated errcheck checks + - G404 # Use of math/rand for RNG + wrapcheck: + ignoreSigs: + - .Errorf( + - errors.New( + - errors.Unwrap( + - .Apply( + revive: + rules: + - name: unused-parameter + severity: warning + disabled: true + arguments: + - allowRegex: "^_" issues: exclude-rules: @@ -35,3 +56,10 @@ issues: - linters: - paralleltest text: "does not use range value in test Run" + - linters: + - errcheck + text: "Error return value of .(tcp|ip|udp).SerializeTo. is not checked" + exclude: + # these should be self-documenting + - "exported const ((TCP|IPv4)(Field|Flag|Option).*|IPFieldVersion) should (have comment|be of the form)" + - "do not define dynamic errors, use wrapped static errors instead:" diff --git a/actions/duplicate_action.go b/actions/duplicate_action.go index 7512144..a2b28ef 100644 --- a/actions/duplicate_action.go +++ b/actions/duplicate_action.go @@ -123,14 +123,14 @@ func ParseDuplicateAction(s *scanner.Scanner) (Action, error) { action.Left = &SendAction{} } else { return nil, fmt.Errorf( - "error parsing first action of duplicate rule: %v", + "error parsing first action of duplicate rule: %w", err) } } if _, err = s.Expect(","); err != nil { return nil, fmt.Errorf( - "unexpected token in duplicate rule: %v", + "unexpected token in duplicate rule: %w", internal.EOFUnexpected(err), ) } @@ -140,14 +140,14 @@ func ParseDuplicateAction(s *scanner.Scanner) (Action, error) { action.Right = &SendAction{} } else { return nil, fmt.Errorf( - "error parsing second action of duplicate rule: %v", + "error parsing second action of duplicate rule: %w", err) } } if _, err = s.Expect(")"); err != nil { return nil, fmt.Errorf( - "unexpected token in duplicate rule: %v", + "unexpected token in duplicate rule: %w", internal.EOFUnexpected(err), ) } diff --git a/actions/fragment_packet.go b/actions/fragment_packet.go index 0124cec..db035c9 100644 --- a/actions/fragment_packet.go +++ b/actions/fragment_packet.go @@ -65,7 +65,6 @@ func (a *FragmentAction) Apply(packet gopacket.Packet) ([]gopacket.Packet, error case layers.LayerTypeTCP: packets, err = fragmentTCPSegment(packet, a.FragSize) default: - // nolint: godox // TODO: should we log this? packets, err = duplicate(packet) } @@ -232,7 +231,7 @@ func FragmentIPPacket(packet gopacket.Packet, fragSize int) ([]gopacket.Packet, buf = buf[:uint16(ofs)+hdrLen+offset] first := gopacket.NewPacket(buf, packet.Layers()[0].LayerType(), gopacket.NoCopy) - if ipv4 := first.Layer(layers.LayerTypeIPv4).(*layers.IPv4); ipv4 != nil { + if ipv4, ok := first.Layer(layers.LayerTypeIPv4).(*layers.IPv4); ok && ipv4 != nil { common.UpdateIPv4Checksum(ipv4) } @@ -253,7 +252,7 @@ func FragmentIPPacket(packet gopacket.Packet, fragSize int) ([]gopacket.Packet, binary.BigEndian.PutUint16(ipv4Buf[6:], flagsAndFrags) second := gopacket.NewPacket(buf, packet.Layers()[0].LayerType(), gopacket.NoCopy) - if ipv4 := second.Layer(layers.LayerTypeIPv4).(*layers.IPv4); ipv4 != nil { + if ipv4, _ := second.Layer(layers.LayerTypeIPv4).(*layers.IPv4); ipv4 != nil { common.UpdateIPv4Checksum(ipv4) } @@ -397,13 +396,13 @@ func ParseFragmentAction(s *scanner.Scanner) (Action, error) { if c, err2 := s.Peek(); err2 == nil && c == ')' { action.SecondFragmentAction = &SendAction{} } else { - return nil, fmt.Errorf("error parsing second action of fragment rule: %v", err) + return nil, fmt.Errorf("error parsing second action of fragment rule: %w", err) } } if _, err := s.Expect(")"); err != nil { return nil, fmt.Errorf( - "unexpected token in fragment rule: %v", + "unexpected token in fragment rule: %w", internal.EOFUnexpected(err), ) } diff --git a/actions/tamper_action.go b/actions/tamper_action.go index bfff1af..16e7a35 100644 --- a/actions/tamper_action.go +++ b/actions/tamper_action.go @@ -1,6 +1,3 @@ -// Package actions describes the actions that can be applied to a given packet. -// -//nolint:depguard,dupword package actions import ( @@ -17,6 +14,7 @@ import ( "github.com/google/gopacket/layers" "github.com/getlantern/geneva/common" + "github.com/getlantern/geneva/internal" "github.com/getlantern/geneva/internal/scanner" ) @@ -27,7 +25,6 @@ const ( TamperCorrupt ) -//nolint:revive var ( ErrInvalidTamperMode = errors.New("invalid tamper mode") ErrInvalidTamperRule = errors.New("invalid tamper rule") @@ -88,6 +85,8 @@ func (a *TamperAction) String() string { // ParseTamperAction parses a string representation of a "tamper" action. // // If the string is malformed, an error will be returned instead. +// +//nolint:errorlint func ParseTamperAction(s *scanner.Scanner) (Action, error) { if _, err := s.Expect("tamper{"); err != nil { return nil, fmt.Errorf("%s: %w", ErrInvalidTamperRule, err) @@ -158,7 +157,7 @@ func ParseTamperAction(s *scanner.Scanner) (Action, error) { } if _, err = s.Expect(")"); err != nil { - return nil, fmt.Errorf("%s: unexpected token: %w", ErrInvalidTamperRule, err) + return nil, fmt.Errorf("%s: unexpected token: %w", ErrInvalidTamperRule, internal.EOFUnexpected(err)) } return newTamperAction(tamperAction) @@ -184,7 +183,6 @@ func newTamperAction(ta TamperAction) (Action, error) { // TCPField is a TCP field that can be modified by a TCPTamperAction. type TCPField uint8 -//nolint:revive const ( // supported TCP options. The other options are apparently obsolete and not used. TCPOptionEol = layers.TCPOptionKindEndList @@ -287,7 +285,7 @@ func NewTCPTamperAction(ta TamperAction) (*TCPTamperAction, error) { switch ta.Mode { case TamperCorrupt: - r := rand.New(rand.NewSource(time.Now().UnixNano())) //nolint:gosec + r := rand.New(rand.NewSource(time.Now().UnixNano())) return &TCPTamperAction{ TamperAction: ta, @@ -340,9 +338,9 @@ func NewTCPTamperAction(ta TamperAction) (*TCPTamperAction, error) { // Apply applies the tamper action to the given packet. func (a *TCPTamperAction) Apply(packet gopacket.Packet) ([]gopacket.Packet, error) { - tcp := packet.Layer(layers.LayerTypeTCP).(*layers.TCP) + tcp, _ := packet.Layer(layers.LayerTypeTCP).(*layers.TCP) if tcp == nil { - return nil, errors.New("packet does not have a TCP layer") //nolint:goerr113 + return nil, errors.New("packet does not have a TCP layer") } tamperTCP(tcp, a.field, a.valueGen) @@ -351,12 +349,12 @@ func (a *TCPTamperAction) Apply(packet gopacket.Packet) ([]gopacket.Packet, erro if strings.HasPrefix(a.Field, "options") { updateTCPDataOffAndChksum(tcp) - if ip := packet.Layer(layers.LayerTypeIPv4).(*layers.IPv4); ip != nil { - updateIPv4LengthAndChksum(ip) + if ip, _ := packet.Layer(layers.LayerTypeIPv4).(*layers.IPv4); ip != nil { + updateIPv4Length(ip) } } - return a.Action.Apply(packet) //nolint:wrapcheck + return a.Action.Apply(packet) } // tamperTCP modifies the given TCP field using the given value generator. @@ -415,7 +413,7 @@ func tamperTCP(tcp *layers.TCP, field TCPField, valueGen tamperValueGen) { // instead of the underlying []byte directly. SerializeTo doesn't write the changes to the raw packet // so we have to copy the formatted bytes back into the packet header. sb := gopacket.NewSerializeBuffer() - tcp.SerializeTo(sb, gopacket.SerializeOptions{}) //nolint:errcheck,gosec + tcp.SerializeTo(sb, gopacket.SerializeOptions{}) tcp.Contents = make([]byte, len(sb.Bytes())) copy(tcp.Contents, sb.Bytes()) } @@ -483,7 +481,6 @@ func updateTCPDataOffAndChksum(tcp *layers.TCP) { // IPv4Field is an IPv4 field that can be modified by an IPv4TamperAction. type IPv4Field uint8 -//nolint:revive const ( // supported IPv4 fields. IPv4FieldSrcIP = iota @@ -540,7 +537,7 @@ func NewIPv4TamperAction(ta TamperAction) (*IPv4TamperAction, error) { switch ta.Mode { case TamperCorrupt: - r := rand.New(rand.NewSource(time.Now().UnixNano())) //nolint:gosec + r := rand.New(rand.NewSource(time.Now().UnixNano())) return &IPv4TamperAction{ TamperAction: ta, @@ -587,15 +584,15 @@ func NewIPv4TamperAction(ta TamperAction) (*IPv4TamperAction, error) { // Apply applies the tamper action to the given packet. func (a *IPv4TamperAction) Apply(packet gopacket.Packet) ([]gopacket.Packet, error) { - ip := packet.Layer(layers.LayerTypeIPv4).(*layers.IPv4) + ip, _ := packet.Layer(layers.LayerTypeIPv4).(*layers.IPv4) if ip == nil { - return nil, errors.New("packet does not have a IPv4 layer") //nolint:goerr113 + return nil, errors.New("packet does not have a IPv4 layer") } tamperIPv4(ip, a.field, a.valueGen) common.UpdateIPv4Checksum(ip) - return a.Action.Apply(packet) //nolint:wrapcheck + return a.Action.Apply(packet) } // tamperIPv4 modifies the given IP field using the given value generator. @@ -630,13 +627,13 @@ func tamperIPv4(ip *layers.IPv4, field IPv4Field, valueGen tamperValueGen) { // let gopacket handle converting modified packet into []byte again, it's just easier // again copy the bytes back into the packet header sb := gopacket.NewSerializeBuffer() - ip.SerializeTo(sb, gopacket.SerializeOptions{}) //nolint:errcheck,gosec + ip.SerializeTo(sb, gopacket.SerializeOptions{}) ip.Contents = make([]byte, len(sb.Bytes())) copy(ip.Contents, sb.Bytes()) } -// updateIPv4LengthAndChksum updates the IPv4 length. -func updateIPv4LengthAndChksum(ip *layers.IPv4) { +// updateIPv4Length updates the IPv4 length. +func updateIPv4Length(ip *layers.IPv4) { length := len(ip.Contents) + len(ip.Payload) ip.Length = uint16(length) binary.BigEndian.PutUint16(ip.Contents[2:4], ip.Length) @@ -682,8 +679,6 @@ func (g *tamperCorruptGen) uint(bitSize int) uint32 { // bytes returns a random byte slice of length n if n <= 20, otherwise it returns a // a random byte slice of random length up to n. -// -//nolint:gosec func (g *tamperCorruptGen) bytes(n int) []byte { if n > 20 { n = g.r.Intn(n) diff --git a/actions/tamper_action_test.go b/actions/tamper_action_test.go index fabbb41..6bb99fd 100644 --- a/actions/tamper_action_test.go +++ b/actions/tamper_action_test.go @@ -1,4 +1,3 @@ -//nolint:depguard,testpackage package actions import ( @@ -95,6 +94,7 @@ func TestTamperTCP(t *testing.T) { valueGen tamperValueGen } + //nolint:forcetypeassert tests := []struct { name string args args diff --git a/common/packet.go b/common/packet.go index 510dd96..9209e17 100644 --- a/common/packet.go +++ b/common/packet.go @@ -1,3 +1,4 @@ +// Package common provides common functions for Geneva. package common import ( @@ -28,7 +29,7 @@ func UpdateIPv4Checksum(ip *layers.IPv4) { // CalculateIPv4Checksum calculates the IPv4 checksum for the given bytes. // copied from gopacket/layers/ip4.go because they didn't export one. for whatever some reason.. func CalculateIPv4Checksum(bytes []byte) uint16 { - buf := make([]byte, len(bytes)) + buf := make([]byte, len(bytes), 60) copy(buf, bytes) // Clear checksum bytes diff --git a/geneva.go b/geneva.go index 6e13346..f555a3f 100644 --- a/geneva.go +++ b/geneva.go @@ -10,7 +10,7 @@ // This package aims to implement the same triggers and actions that the Geneva project's canonical // Python package does. // -// Quick Background +// # Quick Background // // Geneva rules are called "strategies". A strategy consists of zero or more "action trees" that can // be applied to inbound or outbound packets. The actions trees define both a "trigger" and a tree @@ -18,11 +18,11 @@ // or more packets that should replace the original packet, which then can be reinjected into the // host OS' network stack. // -// Strategies, Forests, and Action Trees +// # Strategies, Forests, and Action Trees // // Let's work from the top down. A strategy, conceptually, looks like this: // -// outbound-forest \/ inbound-forest +// outbound-forest \/ inbound-forest // // "outbound-forest" and "inbound-forest" are ordered lists of (trigger, action tree) pairs. The // Geneva paper calls these ordered lists "forests". The outbound and inbound forests are separated @@ -39,11 +39,11 @@ // A real example, taken from https://geneva.cs.umd.edu/papers/geneva_ccs19.pdf (pg 2202), would // look like this: // -// [TCP:flags:S]- -// duplicate( -// tamper{TCP:flags:replace:SA}(send), -// send)-| \/ -// [TCP:flags:R]-drop-| +// [TCP:flags:S]- +// duplicate( +// tamper{TCP:flags:replace:SA}(send), +// send)-| \/ +// [TCP:flags:R]-drop-| // // In this example, the outbound forest would trigger on TCP packets that have just the SYN flag set, // and would perform a few different actions on those packets. The inbound forest would only apply @@ -52,7 +52,7 @@ // // In a forest, each action tree must adhere to the syntax "[trigger]-action-|". // -// Triggers +// # Triggers // // A trigger defines a way to match packets so that an action tree can be applied to them. In the // example above, the first trigger is "[TCP:flags:S]". This is a trigger that matches on the TCP @@ -60,29 +60,29 @@ // will not fire for packets that have, i.e., both SYN and ACK set.) If the packet is not a TCP // packet, or the flags do not match exactly, then this trigger will not fire. // -// Actions +// # Actions // // An action simply encodes steps to manipulate a packet. There are a number of actions described in // the Geneva paper: // -// send +// send // // The "send" action simply yields the given packet. (A quirk—or what the paper deems to be // canonical syntax—is to elide any "send" actions in the action tree. For instance, the action // "duplicate(,)" is equivalent to "duplicate(send,send)". Bear this in mind when reading Geneva // strategies!) // -// drop +// drop // // The "drop" action discards the given packet. // -// duplicate(a1, a2) +// duplicate(a1, a2) // // The "duplicate" action copies the original packet, then applies action a1 to the original and a2 // to the copy. For example, if a1 and a2 are both "send" actions, then the action will yield two // packets identical to the first. // -// fragment{protocol:offset:inOrder}(a1, a2) +// fragment{protocol:offset:inOrder}(a1, a2) // // The "fragment" action takes the original packet and fragments it, applying a1 to one of the // fragments and a2 to the other. Since both the IP and TCP layers support fragmentation, the rule @@ -94,7 +94,7 @@ // that the fragments be returned out-of-order; i.e., reversed, by specifying "False" for the // "inOrder" argument.) // -// tamper{protocol:field:mode[:newValue]}(a1) +// tamper{protocol:field:mode[:newValue]}(a1) // // The "tamper" action takes the original packet and modifies it in some fashion, depending on the // protocol, field, and mode given. There are two modes: replace and corrupt. The "replace" mode @@ -121,9 +121,9 @@ import ( // This is a convenience wrapper for strategy.ParseStrategy(). func NewStrategy(st string) (*strategy.Strategy, error) { s, err := strategy.ParseStrategy(st) - if err != nil { return nil, fmt.Errorf("failed to parse strategy: %w", err) } + return s, nil } diff --git a/internal/internal.go b/internal/internal.go index 110eb1a..818b41e 100644 --- a/internal/internal.go +++ b/internal/internal.go @@ -1,3 +1,4 @@ +// Package internal provides internal Geneva types and functions. package internal import ( diff --git a/internal/scanner/scanner.go b/internal/scanner/scanner.go index bf44e6e..3774734 100644 --- a/internal/scanner/scanner.go +++ b/internal/scanner/scanner.go @@ -1,4 +1,6 @@ -//nolint:godox +// Package scanner is a token scanner tailored to this library. +// +// BUG(sw) this could probably be almost completely replaced with stdlib's text/scanner. package scanner import ( @@ -7,9 +9,7 @@ import ( "unicode" ) -// Scanner is a token scanner tailored to this library. -// -// BUG(sw) this could probably be almost completely replaced with stdlib's text/scanner. +// Scanner is a token scanner. type Scanner struct { rest []rune currentPosition int @@ -20,6 +20,7 @@ func NewScanner(source string) *Scanner { return &Scanner{[]rune(source), 0} } +// Pos returns the current position of the scanner. func (l *Scanner) Pos() int { return l.currentPosition } diff --git a/strategy/strategy.go b/strategy/strategy.go index 142aa64..f125a36 100644 --- a/strategy/strategy.go +++ b/strategy/strategy.go @@ -4,8 +4,7 @@ // outbound packets. The actions trees encode what actions to take on a packet. A strategy, // conceptually, looks like this: // -// -// outbound-forest \/ inbound-forest +// outbound-forest \/ inbound-forest // // "outbound-forest" and "inbound-forest" are ordered lists of "(trigger, action tree)" pairs. The // Geneva paper calls these ordered lists "forests". The outbound and inbound forests are separated @@ -21,12 +20,12 @@ // // A real example, taken from the [original paper][geneva-paper] (pg 2202), would look like this: // -// [TCP:flags:S]- -// duplicate( -// tamper{TCP:flags:replace:SA}( -// send), -// send)-| \/ -// [TCP:flags:R]-drop-| +// [TCP:flags:S]- +// duplicate( +// tamper{TCP:flags:replace:SA}( +// send), +// send)-| \/ +// [TCP:flags:R]-drop-| // // In this example, the outbound forest would trigger on TCP packets that have just the `SYN` flag // set, and would perform a few different actions on those packets. The inbound forest would only @@ -40,9 +39,10 @@ import ( "io" "strings" + "github.com/google/gopacket" + "github.com/getlantern/geneva/actions" "github.com/getlantern/geneva/internal/scanner" - "github.com/google/gopacket" ) // Forest refers to an ordered list of (trigger, action tree) pairs. From 3b5f264383b7760df44067265de4db9d4a2acc89 Mon Sep 17 00:00:00 2001 From: garmr Date: Thu, 19 Oct 2023 10:58:23 -0700 Subject: [PATCH 8/8] fix mixed tabs w/ spaces indentation --- actions/action_test.go | 9 --------- common/packet.go | 13 +++++-------- 2 files changed, 5 insertions(+), 17 deletions(-) diff --git a/actions/action_test.go b/actions/action_test.go index ac7a015..866e40e 100644 --- a/actions/action_test.go +++ b/actions/action_test.go @@ -1,7 +1,6 @@ package actions_test import ( - "encoding/binary" "fmt" "testing" @@ -49,14 +48,6 @@ func TestIPv4HeaderChecksum(t *testing.T) { if chksum != expected { t.Fatalf("expected %#04x, got %#04x", expected, chksum) } - - if val := binary.BigEndian.Uint16(header[10:]); val != expected { - t.Fatalf("expected %#04x in header, got %#04x", expected, val) - } - - if !actions.VerifyIPv4Checksum(header) { - t.Fatal("checksum validation failed") - } } func TestSendAction(t *testing.T) { diff --git a/common/packet.go b/common/packet.go index 9209e17..1bea0d6 100644 --- a/common/packet.go +++ b/common/packet.go @@ -29,18 +29,15 @@ func UpdateIPv4Checksum(ip *layers.IPv4) { // CalculateIPv4Checksum calculates the IPv4 checksum for the given bytes. // copied from gopacket/layers/ip4.go because they didn't export one. for whatever some reason.. func CalculateIPv4Checksum(bytes []byte) uint16 { - buf := make([]byte, len(bytes), 60) - copy(buf, bytes) - // Clear checksum bytes - buf[10] = 0 - buf[11] = 0 + bytes[10] = 0 + bytes[11] = 0 // Compute checksum var csum uint32 - for i := 0; i < len(buf); i += 2 { - csum += uint32(buf[i]) << 8 - csum += uint32(buf[i+1]) + for i := 0; i < len(bytes); i += 2 { + csum += uint32(bytes[i]) << 8 + csum += uint32(bytes[i+1]) } for csum > 0xFFFF {