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
|
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) {
bytes, err := json.Marshal(*desc)
if err != nil {
return "", err
}
return string(bytes), nil
}
func DeserializeSessionDescription(msg string) (*webrtc.SessionDescription, error) {
var parsed map[string]interface{}
err := json.Unmarshal([]byte(msg), &parsed)
if err != nil {
return nil, err
}
if _, ok := parsed["type"]; !ok {
return nil, errors.New("cannot deserialize SessionDescription without type field")
}
if _, ok := parsed["sdp"]; !ok {
return nil, errors.New("cannot deserialize SessionDescription without sdp field")
}
var stype webrtc.SDPType
switch parsed["type"].(string) {
default:
return nil, errors.New("Unknown SDP type")
case "offer":
stype = webrtc.SDPTypeOffer
case "pranswer":
stype = webrtc.SDPTypePranswer
case "answer":
stype = webrtc.SDPTypeAnswer
case "rollback":
stype = webrtc.SDPTypeRollback
}
return &webrtc.SessionDescription{
Type: stype,
SDP: parsed["sdp"].(string),
}, nil
}
// Stolen from https://github.com/golang/go/pull/30278
func IsLocal(ip net.IP) bool {
if ip4 := ip.To4(); ip4 != nil {
// Local IPv4 addresses are defined in https://tools.ietf.org/html/rfc1918
return ip4[0] == 10 ||
(ip4[0] == 172 && ip4[1]&0xf0 == 16) ||
(ip4[0] == 192 && ip4[1] == 168) ||
// Carrier-Grade NAT as per https://tools.ietf.org/htm/rfc6598
(ip4[0] == 100 && ip4[1]&0xc0 == 64) ||
// Dynamic Configuration as per https://tools.ietf.org/htm/rfc3927
(ip4[0] == 169 && ip4[1] == 254)
}
// Local IPv6 addresses are defined in https://tools.ietf.org/html/rfc4193
return len(ip) == net.IPv6len && ip[0]&0xfe == 0xfc
}
// Removes local LAN address ICE candidates
func StripLocalAddresses(str string) string {
var desc sdp.SessionDescription
err := desc.Unmarshal([]byte(str))
if err != nil {
return str
}
for _, m := range desc.MediaDescriptions {
attrs := make([]sdp.Attribute, 0)
for _, a := range m.Attributes {
if a.IsICECandidate() {
c, err := ice.UnmarshalCandidate(a.Value)
if err == nil && c.Type() == ice.CandidateTypeHost {
ip := net.ParseIP(c.Address())
if ip != nil && (IsLocal(ip) || ip.IsUnspecified() || ip.IsLoopback()) {
/* no append in this case */
continue
}
}
}
attrs = append(attrs, a)
}
m.Attributes = attrs
}
bts, err := desc.Marshal()
if err != nil {
return str
}
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
}
|