130 lines
3.0 KiB
Go
130 lines
3.0 KiB
Go
package stun
|
|
|
|
import (
|
|
"crypto/md5"
|
|
"crypto/tls"
|
|
"errors"
|
|
"net"
|
|
"net/url"
|
|
"strings"
|
|
)
|
|
|
|
func Discover(uri string) (net.PacketConn, net.Addr, error) {
|
|
stop := make(chan struct{})
|
|
conn, err := Dial(uri, nil, stop)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
addr, err := conn.Discover()
|
|
if err != nil {
|
|
conn.Close()
|
|
return nil, nil, err
|
|
}
|
|
// TODO: hijack
|
|
// stop reading conn before returning it
|
|
close(stop)
|
|
// note. serveconn/packet func is blocked by read/readfrom at the time
|
|
// we send the signal, which means it will still consume one more
|
|
// packet and we can only read starting from the second packet.
|
|
// (not too much of a problem, since we'll punch a few packets anyway)
|
|
return conn.Conn.(net.PacketConn), addr, nil
|
|
}
|
|
|
|
type AuthMethod func(sess *Session) error
|
|
|
|
// LongTermAuthMethod returns AuthMethod for long-term credentials.
|
|
// Key = MD5(username ":" realm ":" SASLprep(password)).
|
|
// SASLprep is defined in RFC 4013.
|
|
func LongTermAuthMethod(username, password string) AuthMethod {
|
|
return func(sess *Session) error {
|
|
h := md5.New()
|
|
h.Write([]byte(username + ":" + sess.Realm + ":" + password))
|
|
sess.Username = username
|
|
sess.Key = h.Sum(nil)
|
|
return nil
|
|
}
|
|
}
|
|
|
|
// ShotTermAuthMethod returns AuthMethod for short-term credentials.
|
|
// Key = SASLprep(password).
|
|
// SASLprep is defined in RFC 4013.
|
|
func ShortTermAuthMethod(password string) AuthMethod {
|
|
key := []byte(password)
|
|
return func(sess *Session) error {
|
|
sess.Key = key
|
|
return nil
|
|
}
|
|
}
|
|
|
|
func Dial(uri string, config *Config, stop chan struct{}) (*Conn, error) {
|
|
secure, network, addr, auth, err := parseURI(uri)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
var conn net.Conn
|
|
if secure {
|
|
conn, err = tls.Dial(network, addr, nil)
|
|
} else {
|
|
if strings.HasPrefix(network, "udp") {
|
|
conn, err = dialUDP(network, addr)
|
|
} else {
|
|
conn, err = dialTCP(network, addr)
|
|
}
|
|
}
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if auth != nil {
|
|
config = config.Clone()
|
|
config.AuthMethod = auth
|
|
}
|
|
return NewConn(conn, config, stop), nil
|
|
}
|
|
|
|
func parseURI(uri string) (secure bool, network, addr string, auth AuthMethod, err error) {
|
|
var u *url.URL
|
|
if u, err = url.Parse(uri); err != nil {
|
|
return
|
|
}
|
|
host, port, e := net.SplitHostPort(u.Opaque)
|
|
if e != nil {
|
|
host = u.Opaque
|
|
}
|
|
if a := u.User; a != nil {
|
|
if password, ok := a.Password(); ok {
|
|
auth = LongTermAuthMethod(a.Username(), password)
|
|
} else {
|
|
auth = ShortTermAuthMethod(a.Username())
|
|
}
|
|
}
|
|
network = u.Query().Get("transport")
|
|
if network == "" {
|
|
network = "udp"
|
|
}
|
|
switch u.Scheme {
|
|
case "stun", "turn":
|
|
if port == "" {
|
|
port = "3478"
|
|
}
|
|
switch network {
|
|
case "udp", "udp4", "udp6", "tcp", "tcp4", "tcp6":
|
|
default:
|
|
err = errors.New("stun: unsupported transport: " + network)
|
|
}
|
|
case "stuns", "turns":
|
|
if port == "" {
|
|
port = "5478"
|
|
}
|
|
secure = true
|
|
switch network {
|
|
case "tcp", "tcp4", "tcp6":
|
|
default:
|
|
err = errors.New("stun: unsupported transport: " + network)
|
|
}
|
|
default:
|
|
err = errors.New("stun: unsupported scheme " + u.Scheme)
|
|
}
|
|
addr = net.JoinHostPort(host, port)
|
|
return
|
|
}
|