mirror of
https://github.com/screego/server.git
synced 2025-10-05 16:16:58 +08:00
Add dns fetching for external ip
This commit is contained in:
@@ -30,6 +30,12 @@ func serveCmd(version string) cli.Command {
|
||||
if exit {
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
if _, _, err := conf.TurnIPProvider.Get(); err != nil {
|
||||
// error is already logged by .Get()
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
users, err := auth.ReadPasswordsFile(conf.UsersFile, conf.Secret, conf.SessionTimeoutSeconds)
|
||||
if err != nil {
|
||||
log.Fatal().Str("file", conf.UsersFile).Err(err).Msg("While loading users file")
|
||||
|
@@ -4,7 +4,6 @@ import (
|
||||
"crypto/rand"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
@@ -14,6 +13,7 @@ import (
|
||||
"github.com/joho/godotenv"
|
||||
"github.com/kelseyhightower/envconfig"
|
||||
"github.com/rs/zerolog"
|
||||
"github.com/screego/server/config/ipdns"
|
||||
"github.com/screego/server/config/mode"
|
||||
)
|
||||
|
||||
@@ -61,8 +61,7 @@ type Config struct {
|
||||
|
||||
CheckOrigin func(string) bool `ignored:"true" json:"-"`
|
||||
TurnExternal bool `ignored:"true"`
|
||||
TurnIPV4 net.IP `ignored:"true"`
|
||||
TurnIPV6 net.IP `ignored:"true"`
|
||||
TurnIPProvider ipdns.Provider `ignored:"true"`
|
||||
TurnPort string `ignored:"true"`
|
||||
|
||||
CloseRoomWhenOwnerLeaves bool `default:"true" split_words:"true"`
|
||||
@@ -187,7 +186,7 @@ func Get() (Config, []FutureLog) {
|
||||
logs = append(logs, futureFatal("SCREEGO_EXTERNAL_IP and SCREEGO_TURN_EXTERNAL_IP must not be both set"))
|
||||
}
|
||||
|
||||
config.TurnIPV4, config.TurnIPV6, errs = validateExternalIP(config.TurnExternalIP, "SCREEGO_TURN_EXTERNAL_IP")
|
||||
config.TurnIPProvider, errs = parseIPProvider(config.TurnExternalIP, "SCREEGO_TURN_EXTERNAL_IP")
|
||||
config.TurnPort = config.TurnExternalPort
|
||||
config.TurnExternal = true
|
||||
logs = append(logs, errs...)
|
||||
@@ -195,7 +194,7 @@ func Get() (Config, []FutureLog) {
|
||||
logs = append(logs, futureFatal("SCREEGO_TURN_EXTERNAL_SECRET must be set if external TURN server is used"))
|
||||
}
|
||||
} else if len(config.ExternalIP) > 0 {
|
||||
config.TurnIPV4, config.TurnIPV6, errs = validateExternalIP(config.ExternalIP, "SCREEGO_EXTERNAL_IP")
|
||||
config.TurnIPProvider, errs = parseIPProvider(config.ExternalIP, "SCREEGO_EXTERNAL_IP")
|
||||
logs = append(logs, errs...)
|
||||
split := strings.Split(config.TurnAddress, ":")
|
||||
config.TurnPort = split[len(split)-1]
|
||||
@@ -222,50 +221,6 @@ func Get() (Config, []FutureLog) {
|
||||
return config, logs
|
||||
}
|
||||
|
||||
func validateExternalIP(ips []string, config string) (net.IP, net.IP, []FutureLog) {
|
||||
if len(ips) == 0 {
|
||||
return nil, nil, nil
|
||||
}
|
||||
|
||||
first := ips[0]
|
||||
|
||||
firstParsed := net.ParseIP(first)
|
||||
if firstParsed == nil || first == "0.0.0.0" {
|
||||
return nil, nil, []FutureLog{futureFatal(fmt.Sprintf("invalid %s: %s", config, first))}
|
||||
}
|
||||
firstIsIP4 := firstParsed.To4() != nil
|
||||
|
||||
if len(ips) == 1 {
|
||||
if firstIsIP4 {
|
||||
return firstParsed, nil, nil
|
||||
}
|
||||
return nil, firstParsed, nil
|
||||
}
|
||||
|
||||
second := ips[1]
|
||||
|
||||
secondParsed := net.ParseIP(second)
|
||||
if secondParsed == nil || second == "0.0.0.0" {
|
||||
return nil, nil, []FutureLog{futureFatal(fmt.Sprintf("invalid %s: %s", config, second))}
|
||||
}
|
||||
|
||||
secondIsIP4 := secondParsed.To4() != nil
|
||||
|
||||
if firstIsIP4 == secondIsIP4 {
|
||||
return nil, nil, []FutureLog{futureFatal(fmt.Sprintf("invalid %s: the ips must be of different type ipv4/ipv6", config))}
|
||||
}
|
||||
|
||||
if len(ips) > 2 {
|
||||
return nil, nil, []FutureLog{futureFatal(fmt.Sprintf("invalid %s: too many ips supplied", config))}
|
||||
}
|
||||
|
||||
if !firstIsIP4 {
|
||||
return secondParsed, firstParsed, nil
|
||||
}
|
||||
|
||||
return firstParsed, secondParsed, nil
|
||||
}
|
||||
|
||||
func getExecutableOrWorkDir() (string, *FutureLog) {
|
||||
dir, err := getExecutableDir()
|
||||
// when using `go run main.go` the executable lives in th temp directory therefore the env.development
|
||||
|
92
config/ip.go
Normal file
92
config/ip.go
Normal file
@@ -0,0 +1,92 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/screego/server/config/ipdns"
|
||||
)
|
||||
|
||||
func parseIPProvider(ips []string, config string) (ipdns.Provider, []FutureLog) {
|
||||
if len(ips) == 0 {
|
||||
panic("must have at least one ip")
|
||||
}
|
||||
|
||||
first := ips[0]
|
||||
if strings.HasPrefix(first, "dns:") {
|
||||
if len(ips) > 1 {
|
||||
return nil, []FutureLog{futureFatal(fmt.Sprintf("invalid %s: when dns server is specified, only one value is allowed", config))}
|
||||
}
|
||||
|
||||
return parseDNS(strings.TrimPrefix(first, "dns:")), nil
|
||||
}
|
||||
|
||||
return parseStatic(ips, config)
|
||||
}
|
||||
|
||||
func parseStatic(ips []string, config string) (*ipdns.Static, []FutureLog) {
|
||||
var static ipdns.Static
|
||||
|
||||
firstV4, errs := applyIPTo(config, ips[0], &static)
|
||||
if errs != nil {
|
||||
return nil, errs
|
||||
}
|
||||
|
||||
if len(ips) == 1 {
|
||||
return &static, nil
|
||||
}
|
||||
|
||||
secondV4, errs := applyIPTo(config, ips[1], &static)
|
||||
if errs != nil {
|
||||
return nil, errs
|
||||
}
|
||||
|
||||
if firstV4 == secondV4 {
|
||||
return nil, []FutureLog{futureFatal(fmt.Sprintf("invalid %s: the ips must be of different type ipv4/ipv6", config))}
|
||||
}
|
||||
|
||||
if len(ips) > 2 {
|
||||
return nil, []FutureLog{futureFatal(fmt.Sprintf("invalid %s: too many ips supplied", config))}
|
||||
}
|
||||
|
||||
return &static, nil
|
||||
}
|
||||
|
||||
func applyIPTo(config, ip string, static *ipdns.Static) (bool, []FutureLog) {
|
||||
parsed := net.ParseIP(ip)
|
||||
if parsed == nil || ip == "0.0.0.0" {
|
||||
return false, []FutureLog{futureFatal(fmt.Sprintf("invalid %s: %s", config, ip))}
|
||||
}
|
||||
|
||||
v4 := parsed.To4() != nil
|
||||
if v4 {
|
||||
static.V4 = parsed
|
||||
} else {
|
||||
static.V6 = parsed
|
||||
}
|
||||
return v4, nil
|
||||
}
|
||||
|
||||
func parseDNS(dnsString string) *ipdns.DNS {
|
||||
var dns ipdns.DNS
|
||||
|
||||
parts := strings.SplitN(dnsString, "@", 2)
|
||||
|
||||
dns.Domain = parts[0]
|
||||
dns.DNS = "system"
|
||||
if len(parts) == 2 {
|
||||
dns.DNS = parts[1]
|
||||
dns.Resolver = &net.Resolver{
|
||||
PreferGo: true,
|
||||
Dial: func(ctx context.Context, network, address string) (net.Conn, error) {
|
||||
d := net.Dialer{Timeout: 10 * time.Second}
|
||||
return d.DialContext(ctx, network, parts[1])
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
return &dns
|
||||
}
|
77
config/ipdns/dns.go
Normal file
77
config/ipdns/dns.go
Normal file
@@ -0,0 +1,77 @@
|
||||
package ipdns
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"net"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
type DNS struct {
|
||||
sync.Mutex
|
||||
|
||||
DNS string
|
||||
Resolver *net.Resolver
|
||||
Domain string
|
||||
|
||||
refetch time.Time
|
||||
v4 net.IP
|
||||
v6 net.IP
|
||||
err error
|
||||
}
|
||||
|
||||
func (s *DNS) Get() (net.IP, net.IP, error) {
|
||||
s.Lock()
|
||||
defer s.Unlock()
|
||||
|
||||
if s.refetch.Before(time.Now()) {
|
||||
oldV4, oldV6 := s.v4, s.v6
|
||||
s.v4, s.v6, s.err = s.lookup()
|
||||
if s.err == nil {
|
||||
if !oldV4.Equal(s.v4) || !oldV6.Equal(s.v6) {
|
||||
log.Info().Str("v4", s.v4.String()).
|
||||
Str("v6", s.v6.String()).
|
||||
Str("domain", s.Domain).
|
||||
Str("dns", s.DNS).
|
||||
Msg("DNS External IP")
|
||||
}
|
||||
s.refetch = time.Now().Add(time.Minute)
|
||||
} else {
|
||||
// don't spam the dns server
|
||||
s.refetch = time.Now().Add(time.Second)
|
||||
log.Err(s.err).Str("domain", s.Domain).Str("dns", s.DNS).Msg("DNS External IP")
|
||||
}
|
||||
}
|
||||
|
||||
return s.v4, s.v6, s.err
|
||||
}
|
||||
|
||||
func (s *DNS) lookup() (net.IP, net.IP, error) {
|
||||
ips, err := s.Resolver.LookupIP(context.Background(), "ip", s.Domain)
|
||||
if err != nil {
|
||||
if dns, ok := err.(*net.DNSError); ok && s.DNS != "system" {
|
||||
dns.Server = ""
|
||||
}
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
var v4, v6 net.IP
|
||||
for _, ip := range ips {
|
||||
isV6 := strings.Contains(ip.String(), ":")
|
||||
if isV6 && v6 == nil {
|
||||
v6 = ip
|
||||
} else if !isV6 && v4 == nil {
|
||||
v4 = ip
|
||||
}
|
||||
}
|
||||
|
||||
if v4 == nil && v6 == nil {
|
||||
return nil, nil, errors.New("dns record doesn't have an A or AAAA record")
|
||||
}
|
||||
|
||||
return v4, v6, nil
|
||||
}
|
7
config/ipdns/provider.go
Normal file
7
config/ipdns/provider.go
Normal file
@@ -0,0 +1,7 @@
|
||||
package ipdns
|
||||
|
||||
import "net"
|
||||
|
||||
type Provider interface {
|
||||
Get() (net.IP, net.IP, error)
|
||||
}
|
12
config/ipdns/static.go
Normal file
12
config/ipdns/static.go
Normal file
@@ -0,0 +1,12 @@
|
||||
package ipdns
|
||||
|
||||
import "net"
|
||||
|
||||
type Static struct {
|
||||
V4 net.IP
|
||||
V6 net.IP
|
||||
}
|
||||
|
||||
func (s *Static) Get() (net.IP, net.IP, error) {
|
||||
return s.V4, s.V6, nil
|
||||
}
|
@@ -4,7 +4,12 @@
|
||||
# to find your external ip.
|
||||
# curl 'https://api.ipify.org'
|
||||
# Example:
|
||||
# 192.168.178.2,2a01:c22:a87c:e500:2d8:61ff:fec7:f92a
|
||||
# SCREEGO_EXTERNAL_IP=192.168.178.2,2a01:c22:a87c:e500:2d8:61ff:fec7:f92a
|
||||
#
|
||||
# If the server doesn't have a static ip, the ip can be obtained via a domain:
|
||||
# SCREEGO_EXTERNAL_IP=dns:app.screego.net
|
||||
# You can also specify the dns server to use
|
||||
# SCREEGO_EXTERNAL_IP=dns:app.screego.net@9.9.9.9
|
||||
SCREEGO_EXTERNAL_IP=
|
||||
|
||||
# A secret which should be unique. Is used for cookie authentication.
|
||||
@@ -47,7 +52,12 @@ SCREEGO_TURN_STRICT_AUTH=true
|
||||
# to find your external ip.
|
||||
# curl 'https://api.ipify.org'
|
||||
# Example:
|
||||
# 192.168.178.2,2a01:c22:a87c:e500:2d8:61ff:fec7:f92a
|
||||
# SCREEGO_TURN_EXTERNAL_IP=192.168.178.2,2a01:c22:a87c:e500:2d8:61ff:fec7:f92a
|
||||
#
|
||||
# If the turn server doesn't have a static ip, the ip can be obtained via a domain:
|
||||
# SCREEGO_TURN_EXTERNAL_IP=dns:turn.screego.net
|
||||
# You can also specify the dns server to use
|
||||
# SCREEGO_TURN_EXTERNAL_IP=dns:turn.screego.net@9.9.9.9
|
||||
SCREEGO_TURN_EXTERNAL_IP=
|
||||
|
||||
# The port the external TURN server listens on.
|
||||
|
@@ -12,6 +12,7 @@ import (
|
||||
"github.com/pion/turn/v2"
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/screego/server/config"
|
||||
"github.com/screego/server/config/ipdns"
|
||||
"github.com/screego/server/util"
|
||||
)
|
||||
|
||||
@@ -39,9 +40,8 @@ type Entry struct {
|
||||
const Realm = "screego"
|
||||
|
||||
type Generator struct {
|
||||
ipv4 net.IP
|
||||
ipv6 net.IP
|
||||
turn.RelayAddressGenerator
|
||||
IPProvider ipdns.Provider
|
||||
}
|
||||
|
||||
func (r *Generator) AllocatePacketConn(network string, requestedPort int) (net.PacketConn, net.Addr, error) {
|
||||
@@ -50,10 +50,16 @@ func (r *Generator) AllocatePacketConn(network string, requestedPort int) (net.P
|
||||
return conn, addr, err
|
||||
}
|
||||
relayAddr := *addr.(*net.UDPAddr)
|
||||
if r.ipv6 == nil || (relayAddr.IP.To4() != nil && r.ipv4 != nil) {
|
||||
relayAddr.IP = r.ipv4
|
||||
|
||||
v4, v6, err := r.IPProvider.Get()
|
||||
if err != nil {
|
||||
return conn, addr, err
|
||||
}
|
||||
|
||||
if v6 == nil || (relayAddr.IP.To4() != nil && v4 != nil) {
|
||||
relayAddr.IP = v4
|
||||
} else {
|
||||
relayAddr.IP = r.ipv6
|
||||
relayAddr.IP = v6
|
||||
}
|
||||
if err == nil {
|
||||
log.Debug().Str("addr", addr.String()).Str("relayaddr", relayAddr.String()).Msg("TURN allocated")
|
||||
@@ -92,9 +98,8 @@ func newInternalServer(conf config.Config) (Server, error) {
|
||||
}
|
||||
|
||||
gen := &Generator{
|
||||
ipv4: conf.TurnIPV4,
|
||||
ipv6: conf.TurnIPV6,
|
||||
RelayAddressGenerator: generator(conf),
|
||||
IPProvider: conf.TurnIPProvider,
|
||||
}
|
||||
|
||||
_, err = turn.NewServer(turn.ServerConfig{
|
||||
|
@@ -44,11 +44,16 @@ func (e *Join) Execute(rooms *Rooms, current ClientInfo) error {
|
||||
room.notifyInfoChanged()
|
||||
usersJoinedTotal.Inc()
|
||||
|
||||
v4, v6, err := rooms.config.TurnIPProvider.Get()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, user := range room.Users {
|
||||
if current.ID == user.ID || !user.Streaming {
|
||||
continue
|
||||
}
|
||||
room.newSession(user.ID, current.ID, rooms)
|
||||
room.newSession(user.ID, current.ID, rooms, v4, v6)
|
||||
}
|
||||
|
||||
return nil
|
||||
|
@@ -24,11 +24,16 @@ func (e *StartShare) Execute(rooms *Rooms, current ClientInfo) error {
|
||||
|
||||
room.Users[current.ID].Streaming = true
|
||||
|
||||
v4, v6, err := rooms.config.TurnIPProvider.Get()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, user := range room.Users {
|
||||
if current.ID == user.ID {
|
||||
continue
|
||||
}
|
||||
room.newSession(current.ID, user.ID, rooms)
|
||||
room.newSession(current.ID, user.ID, rooms, v4, v6)
|
||||
}
|
||||
|
||||
room.notifyInfoChanged()
|
||||
|
24
ws/room.go
24
ws/room.go
@@ -31,7 +31,7 @@ const (
|
||||
CloseDone = "Read End"
|
||||
)
|
||||
|
||||
func (r *Room) newSession(host, client xid.ID, rooms *Rooms) {
|
||||
func (r *Room) newSession(host, client xid.ID, rooms *Rooms, v4, v6 net.IP) {
|
||||
id := xid.New()
|
||||
r.Sessions[id] = &RoomSession{
|
||||
Host: host,
|
||||
@@ -44,18 +44,18 @@ func (r *Room) newSession(host, client xid.ID, rooms *Rooms) {
|
||||
switch r.Mode {
|
||||
case ConnectionLocal:
|
||||
case ConnectionSTUN:
|
||||
iceHost = []outgoing.ICEServer{{URLs: rooms.addresses("stun", false)}}
|
||||
iceClient = []outgoing.ICEServer{{URLs: rooms.addresses("stun", false)}}
|
||||
iceHost = []outgoing.ICEServer{{URLs: rooms.addresses("stun", v4, v6, false)}}
|
||||
iceClient = []outgoing.ICEServer{{URLs: rooms.addresses("stun", v4, v6, false)}}
|
||||
case ConnectionTURN:
|
||||
hostName, hostPW := rooms.turnServer.Credentials(id.String()+"host", r.Users[host].Addr)
|
||||
clientName, clientPW := rooms.turnServer.Credentials(id.String()+"client", r.Users[client].Addr)
|
||||
iceHost = []outgoing.ICEServer{{
|
||||
URLs: rooms.addresses("turn", true),
|
||||
URLs: rooms.addresses("turn", v4, v6, true),
|
||||
Credential: hostPW,
|
||||
Username: hostName,
|
||||
}}
|
||||
iceClient = []outgoing.ICEServer{{
|
||||
URLs: rooms.addresses("turn", true),
|
||||
URLs: rooms.addresses("turn", v4, v6, true),
|
||||
Credential: clientPW,
|
||||
Username: clientName,
|
||||
}}
|
||||
@@ -64,17 +64,17 @@ func (r *Room) newSession(host, client xid.ID, rooms *Rooms) {
|
||||
r.Users[client].Write <- outgoing.ClientSession{Peer: host, ID: id, ICEServers: iceClient}
|
||||
}
|
||||
|
||||
func (r *Rooms) addresses(prefix string, tcp bool) (result []string) {
|
||||
if r.config.TurnIPV4 != nil {
|
||||
result = append(result, fmt.Sprintf("%s:%s:%s", prefix, r.config.TurnIPV4.String(), r.config.TurnPort))
|
||||
func (r *Rooms) addresses(prefix string, v4, v6 net.IP, tcp bool) (result []string) {
|
||||
if v4 != nil {
|
||||
result = append(result, fmt.Sprintf("%s:%s:%s", prefix, v4.String(), r.config.TurnPort))
|
||||
if tcp {
|
||||
result = append(result, fmt.Sprintf("%s:%s:%s?transport=tcp", prefix, r.config.TurnIPV4.String(), r.config.TurnPort))
|
||||
result = append(result, fmt.Sprintf("%s:%s:%s?transport=tcp", prefix, v4.String(), r.config.TurnPort))
|
||||
}
|
||||
}
|
||||
if r.config.TurnIPV6 != nil {
|
||||
result = append(result, fmt.Sprintf("%s:[%s]:%s", prefix, r.config.TurnIPV6.String(), r.config.TurnPort))
|
||||
if v6 != nil {
|
||||
result = append(result, fmt.Sprintf("%s:[%s]:%s", prefix, v6.String(), r.config.TurnPort))
|
||||
if tcp {
|
||||
result = append(result, fmt.Sprintf("%s:[%s]:%s?transport=tcp", prefix, r.config.TurnIPV6.String(), r.config.TurnPort))
|
||||
result = append(result, fmt.Sprintf("%s:[%s]:%s?transport=tcp", prefix, v6.String(), r.config.TurnPort))
|
||||
}
|
||||
}
|
||||
return
|
||||
|
Reference in New Issue
Block a user