diff options
author | Michael Pu <michael.pu@uwaterloo.ca> | 2024-02-21 01:30:37 -0500 |
---|---|---|
committer | Michael Pu <michael.pu@uwaterloo.ca> | 2024-03-09 13:36:25 -0500 |
commit | b512e242e88ec2b357314662106da034a2f81bbd (patch) | |
tree | 60aed640342291dcab7a0205b2ac5dba1c7938e1 /common | |
parent | fe56eaddf42c17be58e2fe0a6e7e99eadaa5b1f2 (diff) | |
download | snowflake-b512e242e88ec2b357314662106da034a2f81bbd.tar.gz snowflake-b512e242e88ec2b357314662106da034a2f81bbd.zip |
Implement better client IP per rendezvous method tracking for clients
Implement better client IP per rendezvous method tracking for clients
Add tests for added code, fix existing tests
chore(deps): update module github.com/miekg/dns to v1.1.58
Implement better client IP tracking for http and ampcache
Add tests for added code, fix existing tests
Implement GetCandidateAddrs from SDP
Add getting client IP for SQS
Bug fixes
Bug fix for tests
Diffstat (limited to 'common')
-rw-r--r-- | common/util/util.go | 68 | ||||
-rw-r--r-- | common/util/util_test.go | 47 |
2 files changed, 115 insertions, 0 deletions
diff --git a/common/util/util.go b/common/util/util.go index 00f7302..844ef2f 100644 --- a/common/util/util.go +++ b/common/util/util.go @@ -3,11 +3,16 @@ package util import ( "encoding/json" "errors" + "log" "net" + "net/http" + "slices" + "sort" "github.com/pion/ice/v2" "github.com/pion/sdp/v3" "github.com/pion/webrtc/v3" + "github.com/realclientip/realclientip-go" ) func SerializeSessionDescription(desc *webrtc.SessionDescription) (string, error) { @@ -97,3 +102,66 @@ func StripLocalAddresses(str string) string { } return string(bts) } + +// Attempts to retrieve the client IP of where the HTTP request originating. +// There is no standard way to do this since the original client IP can be included in a number of different headers, +// depending on the proxies and load balancers between the client and the server. We attempt to check as many of these +// headers as possible to determine a "best guess" of the client IP +// Using this as a reference: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Forwarded +func GetClientIp(req *http.Request) string { + // We check the "Fowarded" header first, followed by the "X-Forwarded-For" header, and then use the "RemoteAddr" as + // a last resort. We use the leftmost address since it is the closest one to the client. + strat := realclientip.NewChainStrategy( + realclientip.Must(realclientip.NewLeftmostNonPrivateStrategy("Forwarded")), + realclientip.Must(realclientip.NewLeftmostNonPrivateStrategy("X-Forwarded-For")), + realclientip.RemoteAddrStrategy{}, + ) + clientIp := strat.ClientIP(req.Header, req.RemoteAddr) + return clientIp +} + +// Returns a list of IP addresses of ICE candidates, roughly in descending order for accuracy for geolocation +func GetCandidateAddrs(sdpStr string) []net.IP { + var desc sdp.SessionDescription + err := desc.Unmarshal([]byte(sdpStr)) + if err != nil { + log.Printf("GetCandidateAddrs: failed to unmarshal SDP: %v\n", err) + return []net.IP{} + } + + iceCandidates := make([]ice.Candidate, 0) + + for _, m := range desc.MediaDescriptions { + for _, a := range m.Attributes { + if a.IsICECandidate() { + c, err := ice.UnmarshalCandidate(a.Value) + if err == nil { + iceCandidates = append(iceCandidates, c) + } + } + } + } + + // ICE candidates are first sorted in asecending order of priority, to match convention of providing a custom Less + // function to sort + sort.Slice(iceCandidates, func(i, j int) bool { + if iceCandidates[i].Type() != iceCandidates[j].Type() { + // Sort by candidate type first, in the order specified in https://datatracker.ietf.org/doc/html/rfc8445#section-5.1.2.2 + // Higher priority candidate types are more efficient, which likely means they are closer to the client + // itself, providing a more accurate result for geolocation + return ice.CandidateType(iceCandidates[i].Type().Preference()) < ice.CandidateType(iceCandidates[j].Type().Preference()) + } + // Break ties with the ICE candidate's priority property + return iceCandidates[i].Priority() < iceCandidates[j].Priority() + }) + slices.Reverse(iceCandidates) + + sortedIpAddr := make([]net.IP, 0) + for _, c := range iceCandidates { + ip := net.ParseIP(c.Address()) + if ip != nil { + sortedIpAddr = append(sortedIpAddr, ip) + } + } + return sortedIpAddr +} diff --git a/common/util/util_test.go b/common/util/util_test.go index 9d52f62..701a4d6 100644 --- a/common/util/util_test.go +++ b/common/util/util_test.go @@ -1,6 +1,8 @@ package util import ( + "net" + "net/http" "testing" . "github.com/smartystreets/goconvey/convey" @@ -25,4 +27,49 @@ func TestUtil(t *testing.T) { So(StripLocalAddresses(offer), ShouldEqual, offerStart+goodCandidate+offerEnd) }) + + Convey("GetClientIp", t, func() { + // Should use Forwarded header + req1, _ := http.NewRequest("GET", "https://example.com", nil) + req1.Header.Add("X-Forwarded-For", "1.1.1.1, 2001:db8:cafe::99%eth0, 3.3.3.3, 192.168.1.1") + req1.Header.Add("Forwarded", `For=fe80::abcd;By=fe80::1234, Proto=https;For=::ffff:188.0.2.128, For="[2001:db8:cafe::17]:4848", For=fc00::1`) + req1.RemoteAddr = "192.168.1.2:8888" + So(GetClientIp(req1), ShouldEqual, "188.0.2.128") + + // Should use X-Forwarded-For header + req2, _ := http.NewRequest("GET", "https://example.com", nil) + req2.Header.Add("X-Forwarded-For", "1.1.1.1, 2001:db8:cafe::99%eth0, 3.3.3.3, 192.168.1.1") + req2.RemoteAddr = "192.168.1.2:8888" + So(GetClientIp(req2), ShouldEqual, "1.1.1.1") + + // Should use RemoteAddr + req3, _ := http.NewRequest("GET", "https://example.com", nil) + req3.RemoteAddr = "192.168.1.2:8888" + So(GetClientIp(req3), ShouldEqual, "192.168.1.2") + + // Should return empty client IP + req4, _ := http.NewRequest("GET", "https://example.com", nil) + So(GetClientIp(req4), ShouldEqual, "") + }) + + Convey("GetCandidateAddrs", t, func() { + // Should prioritize type in the following order: https://datatracker.ietf.org/doc/html/rfc8445#section-5.1.2.2 + // Break ties using priority value + const offerStart = "v=0\r\no=- 4358805017720277108 2 IN IP4 8.8.8.8\r\ns=-\r\nt=0 0\r\na=group:BUNDLE data\r\na=msid-semantic: WMS\r\nm=application 56688 DTLS/SCTP 5000\r\nc=IN IP4 8.8.8.8\r\n" + const offerEnd = "a=ice-ufrag:aMAZ\r\na=ice-pwd:jcHb08Jjgrazp2dzjdrvPPvV\r\na=ice-options:trickle\r\na=fingerprint:sha-256 C8:88:EE:B9:E7:02:2E:21:37:ED:7A:D1:EB:2B:A3:15:A2:3B:5B:1C:3D:D4:D5:1F:06:CF:52:40:03:F8:DD:66\r\na=setup:actpass\r\na=mid:data\r\na=sctpmap:5000 webrtc-datachannel 1024\r\n" + + const sdp = offerStart + "a=candidate:3769337065 1 udp 2122260223 8.8.8.8 56688 typ prflx\r\n" + + "a=candidate:3769337065 1 udp 2122260223 129.97.124.13 56688 typ relay\r\n" + + "a=candidate:3769337065 1 udp 2122260223 129.97.124.14 56688 typ srflx\r\n" + + "a=candidate:3769337065 1 udp 2122260223 129.97.124.15 56688 typ host\r\n" + + "a=candidate:3769337065 1 udp 2122260224 129.97.124.16 56688 typ host\r\n" + offerEnd + + So(GetCandidateAddrs(sdp), ShouldEqual, []net.IP{ + net.ParseIP("129.97.124.16"), + net.ParseIP("129.97.124.15"), + net.ParseIP("8.8.8.8"), + net.ParseIP("129.97.124.14"), + net.ParseIP("129.97.124.13"), + }) + }) } |