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
|
package xff
import (
"log"
"net"
"net/http"
"os"
"strings"
)
// list of private subnets
var privateMasks, _ = toMasks([]string{
"127.0.0.0/8",
"10.0.0.0/8",
"172.16.0.0/12",
"192.168.0.0/16",
"fc00::/7",
})
// converts a list of subnets' string to a list of net.IPNet.
func toMasks(ips []string) (masks []net.IPNet, err error) {
for _, cidr := range ips {
var network *net.IPNet
_, network, err = net.ParseCIDR(cidr)
if err != nil {
return
}
masks = append(masks, *network)
}
return
}
// checks if a net.IP is in a list of net.IPNet
func ipInMasks(ip net.IP, masks []net.IPNet) bool {
for _, mask := range masks {
if mask.Contains(ip) {
return true
}
}
return false
}
// IsPublicIP returns true if the given IP can be routed on the Internet.
func IsPublicIP(ip net.IP) bool {
if !ip.IsGlobalUnicast() {
return false
}
return !ipInMasks(ip, privateMasks)
}
// Parse parses the value of the X-Forwarded-For Header and returns the IP address.
func Parse(ipList string) string {
for _, ip := range strings.Split(ipList, ",") {
ip = strings.TrimSpace(ip)
if IP := net.ParseIP(ip); IP != nil && IsPublicIP(IP) {
return ip
}
}
return ""
}
// GetRemoteAddr parses the given request, resolves the X-Forwarded-For header
// and returns the resolved remote address.
func GetRemoteAddr(r *http.Request) string {
return GetRemoteAddrIfAllowed(r, func(sip string) bool { return true })
}
// GetRemoteAddrIfAllowed parses the given request, resolves the X-Forwarded-For header
// and returns the resolved remote address if allowed.
func GetRemoteAddrIfAllowed(r *http.Request, allowed func(sip string) bool) string {
if xffh := r.Header.Get("X-Forwarded-For"); xffh != "" {
if sip, sport, err := net.SplitHostPort(r.RemoteAddr); err == nil && sip != "" {
if allowed(sip) {
if xip := Parse(xffh); xip != "" {
return net.JoinHostPort(xip, sport)
}
}
}
}
return r.RemoteAddr
}
// Options is a configuration container to setup the XFF middleware.
type Options struct {
// AllowedSubnets is a list of Subnets from which we will accept the
// X-Forwarded-For header.
// If this list is empty we will accept every Subnets (default).
AllowedSubnets []string
// Debugging flag adds additional output to debug server side XFF issues.
Debug bool
}
// XFF http handler
type XFF struct {
// Debug logger
Log *log.Logger
// Set to true if all IPs or Subnets are allowed.
allowAll bool
// List of IP subnets that are allowed.
allowedMasks []net.IPNet
}
// New creates a new XFF handler with the provided options.
func New(options Options) (*XFF, error) {
allowedMasks, err := toMasks(options.AllowedSubnets)
if err != nil {
return nil, err
}
xff := &XFF{
allowAll: len(options.AllowedSubnets) == 0,
allowedMasks: allowedMasks,
}
if options.Debug {
xff.Log = log.New(os.Stdout, "[xff] ", log.LstdFlags)
}
return xff, nil
}
// Default creates a new XFF handler with default options.
func Default() (*XFF, error) {
return New(Options{})
}
// Handler updates RemoteAdd from X-Fowarded-For Headers.
func (xff *XFF) Handler(h http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
r.RemoteAddr = GetRemoteAddrIfAllowed(r, xff.allowed)
h.ServeHTTP(w, r)
})
}
// Negroni compatible interface
func (xff *XFF) ServeHTTP(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
r.RemoteAddr = GetRemoteAddrIfAllowed(r, xff.allowed)
next(w, r)
}
// HandlerFunc provides Martini compatible handler
func (xff *XFF) HandlerFunc(w http.ResponseWriter, r *http.Request) {
r.RemoteAddr = GetRemoteAddrIfAllowed(r, xff.allowed)
}
// checks that the IP is allowed.
func (xff *XFF) allowed(sip string) bool {
if xff.allowAll {
return true
} else if ip := net.ParseIP(sip); ip != nil && ipInMasks(ip, xff.allowedMasks) {
return true
}
return false
}
// convenience method. checks if debugging is turned on before printing
func (xff *XFF) logf(format string, a ...interface{}) {
if xff.Log != nil {
xff.Log.Printf(format, a...)
}
}
|