File: CONTRIBUTING.md

package info (click to toggle)
gopacket 1.1.14-3
  • links: PTS, VCS
  • area: main
  • in suites: buster, buster-backports
  • size: 3,288 kB
  • sloc: sh: 283; python: 75; makefile: 15; asm: 2
file content (215 lines) | stat: -rw-r--r-- 8,921 bytes parent folder | download | duplicates (5)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
Contributing To gopacket
========================

So you've got some code and you'd like it to be part of gopacket... wonderful!
We're happy to accept contributions, whether they're fixes to old protocols, new
protocols entirely, or anything else you think would improve the gopacket
library.  This document is designed to help you to do just that.

The first section deals with the plumbing:  how to actually get a change
submitted.

The second section deals with coding style... Go is great in that it
has a uniform style implemented by 'go fmt', but there's still some decisions
we've made that go above and beyond, and if you follow them, they won't come up
in your code review.

The third section deals with some of the implementation decisions we've made,
which may help you to understand the current code and which we may ask you to
conform to (or provide compelling reasons for ignoring).

Overall, we hope this document will help you to understand our system and write
great code which fits in, and help us to turn around on your code review quickly
so the code can make it into the master branch as quickly as possible.


How To Submit Code
------------------

We use github.com's Pull Request feature to receive code contributions from
external contributors.  See
https://help.github.com/articles/creating-a-pull-request/ for details on
how to create a request.

Also, there's a local script `gc` in the base directory of GoPacket that
runs a local set of checks, which should give you relatively high confidence
that your pull won't fail github pull checks.

```sh
go get github.com/google/gopacket
cd $GOROOT/src/pkg/github.com/google/gopacket
git checkout -b <mynewfeature>  # create a new branch to work from
... code code code ...
./gc  # Run this to do local commits, it performs a number of checks
```

To sum up:

* DO
    + Pull down the latest version.
    + Make a feature-specific branch.
    + Code using the style and methods discussed in the rest of this document.
    + Use the ./gc command to do local commits or check correctness.
    + Push your new feature branch up to github.com, as a pull request.
    + Handle comments and requests from reviewers, pushing new commits up to
      your feature branch as problems are addressed.
    + Put interesting comments and discussions into commit comments.
* DON'T
    + Push to someone else's branch without their permission.


Coding Style
------------

* Go code must be run through `go fmt`, `go vet`, and `golint`
* Follow http://golang.org/doc/effective_go.html as much as possible.
    + In particular, http://golang.org/doc/effective_go.html#mixed-caps.  Enums
      should be be CamelCase, with acronyms capitalized (TCPSourcePort, vs.
      TcpSourcePort or TCP_SOURCE_PORT).
* Bonus points for giving enum types a String() field.
* Any exported types or functions should have commentary
  (http://golang.org/doc/effective_go.html#commentary)


Coding Methods And Implementation Notes
---------------------------------------

### Error Handling

Many times, you'll be decoding a protocol and run across something bad, a packet
corruption or the like.  How do you handle this?  First off, ALWAYS report the
error.  You can do this either by returning the error from the decode() function
(most common), or if you're up for it you can implement and add an ErrorLayer
through the packet builder (the first method is a simple shortcut that does
exactly this, then stops any future decoding).

Often, you'll already have decode some part of your protocol by the time you hit
your error.  Use your own discretion to determine whether the stuff you've
already decoded should be returned to the caller or not:

```go
func decodeMyProtocol(data []byte, p gopacket.PacketBuilder) error {
  prot := &MyProtocol{}
  if len(data) < 10 {
    // This error occurred before we did ANYTHING, so there's nothing in my
    // protocol that the caller could possibly want.  Just return the error.
    return fmt.Errorf("Length %d less than 10", len(data))
  }
  prot.ImportantField1 = data[:5]
  prot.ImportantField2 = data[5:10]
  // At this point, we've already got enough information in 'prot' to
  // warrant returning it to the caller, so we'll add it now.
  p.AddLayer(prot)
  if len(data) < 15 {
    // We encountered an error later in the packet, but the caller already
    // has the important info we've gleaned so far.
    return fmt.Errorf("Length %d less than 15", len(data))
  }
  prot.ImportantField3 = data[10:15]
  return nil  // We've already added the layer, we can just return success.
}
```

In general, our code follows the approach of returning the first error it
encounters.  In general, we don't trust any bytes after the first error we see.

### What Is A Layer?

The definition of a layer is up to the discretion of the coder.  It should be
something important enough that it's actually useful to the caller (IE: every
TLV value should probably NOT be a layer).  However, it can be more granular
than a single protocol... IPv6 and SCTP both implement many layers to handle the
various parts of the protocol.  Use your best judgement, and prepare to defend
your decisions during code review. ;)

### Performance

We strive to make gopacket as fast as possible while still providing lots of
features.  In general, this means:

* Focus performance tuning on common protocols (IP4/6, TCP, etc), and optimize
  others on an as-needed basis (tons of MPLS on your network?  Time to optimize
  MPLS!)
* Use fast operations.  See the toplevel benchmark_test for benchmarks of some
  of Go's underlying features and types.
* Test your performance changes!  You should use the ./gc script's --benchmark
  flag to submit any performance-related changes.  Use pcap/gopacket_benchmark
  to test your change against a PCAP file based on your traffic patterns.
* Don't be TOO hacky.  Sometimes, removing an unused struct from a field causes
  a huge performance hit, due to the way that Go currently handles its segmented
  stack... don't be afraid to clean it up anyway.  We'll trust the Go compiler
  to get good enough over time to handle this.  Also, this type of
  compiler-specific optimization is very fragile; someone adding a field to an
  entirely different struct elsewhere in the codebase could reverse any gains
  you might achieve by aligning your allocations.
* Try to minimize memory allocations.  If possible, use []byte to reference
  pieces of the input, instead of using string, which requires copying the bytes
  into a new memory allocation.
* Think hard about what should be evaluated lazily vs. not.  In general, a
  layer's struct should almost exactly mirror the layer's frame.  Anything
  that's more interesting should be a function.  This may not always be
  possible, but it's a good rule of thumb.
* Don't fear micro-optimizations.  With the above in mind, we welcome
  micro-optimizations that we think will have positive/neutral impacts on the
  majority of workloads.  A prime example of this is pre-allocating certain
  structs within a larger one:

```go
type MyProtocol struct {
  // Most packets have 1-4 of VeryCommon, so we preallocate it here.
  initialAllocation [4]uint32
  VeryCommon []uint32
}

func decodeMyProtocol(data []byte, p gopacket.PacketBuilder) error {
  prot := &MyProtocol{}
  prot.VeryCommon = proto.initialAllocation[:0]
  for len(data) > 4 {
    field := binary.BigEndian.Uint32(data[:4])
    data = data[4:]
    // Since we're using the underlying initialAllocation, we won't need to
    // allocate new memory for the following append unless we more than 16
    // bytes of data, which should be the uncommon case.
    prot.VeryCommon = append(prot.VeryCommon, field)
  }
  p.AddLayer(prot)
  if len(data) > 0 {
    return fmt.Errorf("MyProtocol packet has %d bytes left after decoding", len(data))
  }
  return nil
}
```

### Slices And Data

If you're pulling a slice from the data you're decoding, don't copy it.  Just
use the slice itself.

```go
type MyProtocol struct {
  A, B net.IP
}
func decodeMyProtocol(data []byte, p gopacket.PacketBuilder) error {
  p.AddLayer(&MyProtocol{
    A: data[:4],
    B: data[4:8],
  })
  return nil
}
```

The caller has already agreed, by using this library, that they won't modify the
set of bytes they pass in to the decoder, or the library has already copied the
set of bytes to a read-only location.  See DecodeOptions.NoCopy for more
information.

### Enums/Types

If a protocol has an integer field (uint8, uint16, etc) with a couple of known
values that mean something special, make it a type.  This allows us to do really
nice things like adding a String() function to them, so we can more easily
display those to users.  Check out layers/enums.go for one example, as well as
layers/icmp.go for layer-specific enums.

When naming things, try for descriptiveness over suscinctness.  For example,
choose DNSResponseRecord over DNSRR.