1package ice 2 3import ( 4 "net" 5 "net/url" 6 "strconv" 7) 8 9// TODO: Migrate address parsing to STUN/TURN 10 11// SchemeType indicates the type of server used in the ice.URL structure. 12type SchemeType int 13 14// Unknown defines default public constant to use for "enum" like struct 15// comparisons when no value was defined. 16const Unknown = iota 17 18const ( 19 // SchemeTypeSTUN indicates the URL represents a STUN server. 20 SchemeTypeSTUN SchemeType = iota + 1 21 22 // SchemeTypeSTUNS indicates the URL represents a STUNS (secure) server. 23 SchemeTypeSTUNS 24 25 // SchemeTypeTURN indicates the URL represents a TURN server. 26 SchemeTypeTURN 27 28 // SchemeTypeTURNS indicates the URL represents a TURNS (secure) server. 29 SchemeTypeTURNS 30) 31 32// NewSchemeType defines a procedure for creating a new SchemeType from a raw 33// string naming the scheme type. 34func NewSchemeType(raw string) SchemeType { 35 switch raw { 36 case "stun": 37 return SchemeTypeSTUN 38 case "stuns": 39 return SchemeTypeSTUNS 40 case "turn": 41 return SchemeTypeTURN 42 case "turns": 43 return SchemeTypeTURNS 44 default: 45 return SchemeType(Unknown) 46 } 47} 48 49func (t SchemeType) String() string { 50 switch t { 51 case SchemeTypeSTUN: 52 return "stun" 53 case SchemeTypeSTUNS: 54 return "stuns" 55 case SchemeTypeTURN: 56 return "turn" 57 case SchemeTypeTURNS: 58 return "turns" 59 default: 60 return ErrUnknownType.Error() 61 } 62} 63 64// ProtoType indicates the transport protocol type that is used in the ice.URL 65// structure. 66type ProtoType int 67 68const ( 69 // ProtoTypeUDP indicates the URL uses a UDP transport. 70 ProtoTypeUDP ProtoType = iota + 1 71 72 // ProtoTypeTCP indicates the URL uses a TCP transport. 73 ProtoTypeTCP 74) 75 76// NewProtoType defines a procedure for creating a new ProtoType from a raw 77// string naming the transport protocol type. 78func NewProtoType(raw string) ProtoType { 79 switch raw { 80 case "udp": 81 return ProtoTypeUDP 82 case "tcp": 83 return ProtoTypeTCP 84 default: 85 return ProtoType(Unknown) 86 } 87} 88 89func (t ProtoType) String() string { 90 switch t { 91 case ProtoTypeUDP: 92 return "udp" 93 case ProtoTypeTCP: 94 return "tcp" 95 default: 96 return ErrUnknownType.Error() 97 } 98} 99 100// URL represents a STUN (rfc7064) or TURN (rfc7065) URL 101type URL struct { 102 Scheme SchemeType 103 Host string 104 Port int 105 Username string 106 Password string 107 Proto ProtoType 108} 109 110// ParseURL parses a STUN or TURN urls following the ABNF syntax described in 111// https://tools.ietf.org/html/rfc7064 and https://tools.ietf.org/html/rfc7065 112// respectively. 113func ParseURL(raw string) (*URL, error) { 114 rawParts, err := url.Parse(raw) 115 if err != nil { 116 return nil, err 117 } 118 119 var u URL 120 u.Scheme = NewSchemeType(rawParts.Scheme) 121 if u.Scheme == SchemeType(Unknown) { 122 return nil, ErrSchemeType 123 } 124 125 var rawPort string 126 if u.Host, rawPort, err = net.SplitHostPort(rawParts.Opaque); err != nil { 127 if e, ok := err.(*net.AddrError); ok { 128 if e.Err == "missing port in address" { 129 nextRawURL := u.Scheme.String() + ":" + rawParts.Opaque 130 switch { 131 case u.Scheme == SchemeTypeSTUN || u.Scheme == SchemeTypeTURN: 132 nextRawURL += ":3478" 133 if rawParts.RawQuery != "" { 134 nextRawURL += "?" + rawParts.RawQuery 135 } 136 return ParseURL(nextRawURL) 137 case u.Scheme == SchemeTypeSTUNS || u.Scheme == SchemeTypeTURNS: 138 nextRawURL += ":5349" 139 if rawParts.RawQuery != "" { 140 nextRawURL += "?" + rawParts.RawQuery 141 } 142 return ParseURL(nextRawURL) 143 } 144 } 145 } 146 return nil, err 147 } 148 149 if u.Host == "" { 150 return nil, ErrHost 151 } 152 153 if u.Port, err = strconv.Atoi(rawPort); err != nil { 154 return nil, ErrPort 155 } 156 157 switch u.Scheme { 158 case SchemeTypeSTUN: 159 qArgs, err := url.ParseQuery(rawParts.RawQuery) 160 if err != nil || len(qArgs) > 0 { 161 return nil, ErrSTUNQuery 162 } 163 u.Proto = ProtoTypeUDP 164 case SchemeTypeSTUNS: 165 qArgs, err := url.ParseQuery(rawParts.RawQuery) 166 if err != nil || len(qArgs) > 0 { 167 return nil, ErrSTUNQuery 168 } 169 u.Proto = ProtoTypeTCP 170 case SchemeTypeTURN: 171 proto, err := parseProto(rawParts.RawQuery) 172 if err != nil { 173 return nil, err 174 } 175 176 u.Proto = proto 177 if u.Proto == ProtoType(Unknown) { 178 u.Proto = ProtoTypeUDP 179 } 180 case SchemeTypeTURNS: 181 proto, err := parseProto(rawParts.RawQuery) 182 if err != nil { 183 return nil, err 184 } 185 186 u.Proto = proto 187 if u.Proto == ProtoType(Unknown) { 188 u.Proto = ProtoTypeTCP 189 } 190 } 191 192 return &u, nil 193} 194 195func parseProto(raw string) (ProtoType, error) { 196 qArgs, err := url.ParseQuery(raw) 197 if err != nil || len(qArgs) > 1 { 198 return ProtoType(Unknown), ErrInvalidQuery 199 } 200 201 var proto ProtoType 202 if rawProto := qArgs.Get("transport"); rawProto != "" { 203 if proto = NewProtoType(rawProto); proto == ProtoType(0) { 204 return ProtoType(Unknown), ErrProtoType 205 } 206 return proto, nil 207 } 208 209 if len(qArgs) > 0 { 210 return ProtoType(Unknown), ErrInvalidQuery 211 } 212 213 return proto, nil 214} 215 216func (u URL) String() string { 217 rawURL := u.Scheme.String() + ":" + net.JoinHostPort(u.Host, strconv.Itoa(u.Port)) 218 if u.Scheme == SchemeTypeTURN || u.Scheme == SchemeTypeTURNS { 219 rawURL += "?transport=" + u.Proto.String() 220 } 221 return rawURL 222} 223 224// IsSecure returns whether the this URL's scheme describes secure scheme or not. 225func (u URL) IsSecure() bool { 226 return u.Scheme == SchemeTypeSTUNS || u.Scheme == SchemeTypeTURNS 227} 228