uri.go 6.0 KB

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