aboutsummaryrefslogtreecommitdiff
path: root/common
diff options
context:
space:
mode:
authormeskio <meskio@torproject.org>2024-03-12 08:28:53 -0300
committermeskio <meskio@torproject.org>2024-03-12 08:28:53 -0300
commitf502eca67d345e0a6b460f2b677d4ad50dbc9709 (patch)
tree57cf2a1831eb86f0b19ff828fa923784187d0ac1 /common
parentd657098340cffa49fcd5400e58cd614e76fd71af (diff)
parent8968535c56e00389b371e7cdb3b1e84628a796ae (diff)
downloadsnowflake-f502eca67d345e0a6b460f2b677d4ad50dbc9709.tar.gz
snowflake-f502eca67d345e0a6b460f2b677d4ad50dbc9709.zip
Merge remote-tracking branch 'origin/mr/258'
Diffstat (limited to 'common')
-rw-r--r--common/util/util.go68
-rw-r--r--common/util/util_test.go47
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"),
+ })
+ })
}