/
httptrace.go
149 lines (138 loc) · 4.16 KB
/
httptrace.go
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
// Unless explicitly stated otherwise all files in this repository are licensed
// under the Apache License Version 2.0.
// This product includes software developed at Datadog (https://www.datadoghq.com/).
// Copyright 2016 Datadog, Inc.
// Package httptrace provides functionalities to trace HTTP requests that are commonly required and used across
// contrib/** integrations.
package httptrace
import (
"context"
"fmt"
"net"
"net/http"
"os"
"strconv"
"strings"
"inet.af/netaddr"
"gopkg.in/DataDog/dd-trace-go.v1/ddtrace"
"gopkg.in/DataDog/dd-trace-go.v1/ddtrace/ext"
"gopkg.in/DataDog/dd-trace-go.v1/ddtrace/tracer"
)
var (
ipv6SpecialNetworks = []*netaddr.IPPrefix{
ippref("fec0::/10"), // site local
}
defaultIPHeaders = []string{
"x-forwarded-for",
"x-real-ip",
"x-client-ip",
"x-forwarded",
"x-cluster-client-ip",
"forwarded-for",
"forwarded",
"via",
"true-client-ip",
}
clientIPHeader = os.Getenv("DD_TRACE_CLIENT_IP_HEADER")
)
// StartRequestSpan starts an HTTP request span with the standard list of HTTP request span tags (http.method, http.url,
// http.useragent). Any further span start option can be added with opts.
func StartRequestSpan(r *http.Request, opts ...ddtrace.StartSpanOption) (tracer.Span, context.Context) {
// Append our span options before the given ones so that the caller can "overwrite" them.
opts = append([]ddtrace.StartSpanOption{
tracer.SpanType(ext.SpanTypeWeb),
tracer.Tag(ext.HTTPMethod, r.Method),
tracer.Tag(ext.HTTPURL, r.URL.Path),
tracer.Tag(ext.HTTPUserAgent, r.UserAgent()),
tracer.Measured(),
}, opts...)
if r.Host != "" {
opts = append([]ddtrace.StartSpanOption{
tracer.Tag("http.host", r.Host),
}, opts...)
}
if ip := getClientIP(r.RemoteAddr, r.Header, clientIPHeader); ip.IsValid() {
opts = append(opts, tracer.Tag(ext.HTTPClientIP, ip.String()))
}
if spanctx, err := tracer.Extract(tracer.HTTPHeadersCarrier(r.Header)); err == nil {
opts = append(opts, tracer.ChildOf(spanctx))
}
return tracer.StartSpanFromContext(r.Context(), "http.request", opts...)
}
// FinishRequestSpan finishes the given HTTP request span and sets the expected response-related tags such as the status
// code. Any further span finish option can be added with opts.
func FinishRequestSpan(s tracer.Span, status int, opts ...tracer.FinishOption) {
var statusStr string
if status == 0 {
statusStr = "200"
} else {
statusStr = strconv.Itoa(status)
}
s.SetTag(ext.HTTPCode, statusStr)
if status >= 500 && status < 600 {
s.SetTag(ext.Error, fmt.Errorf("%s: %s", statusStr, http.StatusText(status)))
}
s.Finish(opts...)
}
// Helper function to return the IP network out of a string.
func ippref(s string) *netaddr.IPPrefix {
if prefix, err := netaddr.ParseIPPrefix(s); err == nil {
return &prefix
}
return nil
}
// getClientIP uses the request headers to resolve the client IP. If a specific header to check is provided through
// DD_CLIENT_IP_HEADER, then only this header is checked.
func getClientIP(remoteAddr string, headers http.Header, clientIPHeader string) netaddr.IP {
ipHeaders := defaultIPHeaders
if len(clientIPHeader) > 0 {
ipHeaders = []string{clientIPHeader}
}
check := func(s string) netaddr.IP {
for _, ipstr := range strings.Split(s, ",") {
ip := parseIP(strings.TrimSpace(ipstr))
if !ip.IsValid() {
continue
}
if isGlobal(ip) {
return ip
}
}
return netaddr.IP{}
}
for _, hdr := range ipHeaders {
if v := headers.Get(hdr); v != "" {
if ip := check(v); ip.IsValid() {
return ip
}
}
}
if remoteIP := parseIP(remoteAddr); remoteIP.IsValid() && isGlobal(remoteIP) {
return remoteIP
}
return netaddr.IP{}
}
func parseIP(s string) netaddr.IP {
if ip, err := netaddr.ParseIP(s); err == nil {
return ip
}
if h, _, err := net.SplitHostPort(s); err == nil {
if ip, err := netaddr.ParseIP(h); err == nil {
return ip
}
}
return netaddr.IP{}
}
func isGlobal(ip netaddr.IP) bool {
//IsPrivate also checks for ipv6 ULA
globalCheck := !ip.IsPrivate() && !ip.IsLoopback() && !ip.IsLinkLocalUnicast()
if !globalCheck || !ip.Is6() {
return globalCheck
}
for _, n := range ipv6SpecialNetworks {
if n.Contains(ip) {
return false
}
}
return globalCheck
}