diff options
Diffstat (limited to 'vendor/github.com/pion/turn/v2/internal/server/turn.go')
-rw-r--r-- | vendor/github.com/pion/turn/v2/internal/server/turn.go | 352 |
1 files changed, 352 insertions, 0 deletions
diff --git a/vendor/github.com/pion/turn/v2/internal/server/turn.go b/vendor/github.com/pion/turn/v2/internal/server/turn.go new file mode 100644 index 0000000..cbac09c --- /dev/null +++ b/vendor/github.com/pion/turn/v2/internal/server/turn.go @@ -0,0 +1,352 @@ +package server + +import ( + "fmt" + "net" + + "github.com/pion/stun" + "github.com/pion/turn/v2/internal/allocation" + "github.com/pion/turn/v2/internal/ipnet" + "github.com/pion/turn/v2/internal/proto" +) + +// // https://tools.ietf.org/html/rfc5766#section-6.2 +func handleAllocateRequest(r Request, m *stun.Message) error { + r.Log.Debugf("received AllocateRequest from %s", r.SrcAddr.String()) + + // 1. The server MUST require that the request be authenticated. This + // authentication MUST be done using the long-term credential + // mechanism of [https://tools.ietf.org/html/rfc5389#section-10.2.2] + // unless the client and server agree to use another mechanism through + // some procedure outside the scope of this document. + messageIntegrity, hasAuth, err := authenticateRequest(r, m, stun.MethodAllocate) + if !hasAuth { + return err + } + + fiveTuple := &allocation.FiveTuple{ + SrcAddr: r.SrcAddr, + DstAddr: r.Conn.LocalAddr(), + Protocol: allocation.UDP, + } + requestedPort := 0 + reservationToken := "" + + badRequestMsg := buildMsg(m.TransactionID, stun.NewType(stun.MethodAllocate, stun.ClassErrorResponse), &stun.ErrorCodeAttribute{Code: stun.CodeBadRequest}) + insufficentCapacityMsg := buildMsg(m.TransactionID, stun.NewType(stun.MethodAllocate, stun.ClassErrorResponse), &stun.ErrorCodeAttribute{Code: stun.CodeInsufficientCapacity}) + + // 2. The server checks if the 5-tuple is currently in use by an + // existing allocation. If yes, the server rejects the request with + // a 437 (Allocation Mismatch) error. + if alloc := r.AllocationManager.GetAllocation(fiveTuple); alloc != nil { + msg := buildMsg(m.TransactionID, stun.NewType(stun.MethodAllocate, stun.ClassErrorResponse), &stun.ErrorCodeAttribute{Code: stun.CodeAllocMismatch}) + return buildAndSendErr(r.Conn, r.SrcAddr, errRelayAlreadyAllocatedForFiveTuple, msg...) + } + + // 3. The server checks if the request contains a REQUESTED-TRANSPORT + // attribute. If the REQUESTED-TRANSPORT attribute is not included + // or is malformed, the server rejects the request with a 400 (Bad + // Request) error. Otherwise, if the attribute is included but + // specifies a protocol other that UDP, the server rejects the + // request with a 442 (Unsupported Transport Protocol) error. + var requestedTransport proto.RequestedTransport + if err = requestedTransport.GetFrom(m); err != nil { + return buildAndSendErr(r.Conn, r.SrcAddr, err, badRequestMsg...) + } else if requestedTransport.Protocol != proto.ProtoUDP { + msg := buildMsg(m.TransactionID, stun.NewType(stun.MethodAllocate, stun.ClassErrorResponse), &stun.ErrorCodeAttribute{Code: stun.CodeUnsupportedTransProto}) + return buildAndSendErr(r.Conn, r.SrcAddr, errRequestedTransportMustBeUDP, msg...) + } + + // 4. The request may contain a DONT-FRAGMENT attribute. If it does, + // but the server does not support sending UDP datagrams with the DF + // bit set to 1 (see Section 12), then the server treats the DONT- + // FRAGMENT attribute in the Allocate request as an unknown + // comprehension-required attribute. + if m.Contains(stun.AttrDontFragment) { + msg := buildMsg(m.TransactionID, stun.NewType(stun.MethodAllocate, stun.ClassErrorResponse), &stun.ErrorCodeAttribute{Code: stun.CodeUnknownAttribute}, &stun.UnknownAttributes{stun.AttrDontFragment}) + return buildAndSendErr(r.Conn, r.SrcAddr, errNoDontFragmentSupport, msg...) + } + + // 5. The server checks if the request contains a RESERVATION-TOKEN + // attribute. If yes, and the request also contains an EVEN-PORT + // attribute, then the server rejects the request with a 400 (Bad + // Request) error. Otherwise, it checks to see if the token is + // valid (i.e., the token is in range and has not expired and the + // corresponding relayed transport address is still available). If + // the token is not valid for some reason, the server rejects the + // request with a 508 (Insufficient Capacity) error. + var reservationTokenAttr proto.ReservationToken + if err = reservationTokenAttr.GetFrom(m); err == nil { + var evenPort proto.EvenPort + if err = evenPort.GetFrom(m); err == nil { + return buildAndSendErr(r.Conn, r.SrcAddr, errRequestWithReservationTokenAndEvenPort, badRequestMsg...) + } + } + + // 6. The server checks if the request contains an EVEN-PORT attribute. + // If yes, then the server checks that it can satisfy the request + // (i.e., can allocate a relayed transport address as described + // below). If the server cannot satisfy the request, then the + // server rejects the request with a 508 (Insufficient Capacity) + // error. + var evenPort proto.EvenPort + if err = evenPort.GetFrom(m); err == nil { + randomPort := 0 + randomPort, err = r.AllocationManager.GetRandomEvenPort() + if err != nil { + return buildAndSendErr(r.Conn, r.SrcAddr, err, insufficentCapacityMsg...) + } + requestedPort = randomPort + reservationToken = randSeq(8) + } + + // 7. At any point, the server MAY choose to reject the request with a + // 486 (Allocation Quota Reached) error if it feels the client is + // trying to exceed some locally defined allocation quota. The + // server is free to define this allocation quota any way it wishes, + // but SHOULD define it based on the username used to authenticate + // the request, and not on the client's transport address. + + // 8. Also at any point, the server MAY choose to reject the request + // with a 300 (Try Alternate) error if it wishes to redirect the + // client to a different server. The use of this error code and + // attribute follow the specification in [RFC5389]. + lifetimeDuration := allocationLifeTime(m) + a, err := r.AllocationManager.CreateAllocation( + fiveTuple, + r.Conn, + requestedPort, + lifetimeDuration) + if err != nil { + return buildAndSendErr(r.Conn, r.SrcAddr, err, insufficentCapacityMsg...) + } + + // Once the allocation is created, the server replies with a success + // response. The success response contains: + // * An XOR-RELAYED-ADDRESS attribute containing the relayed transport + // address. + // * A LIFETIME attribute containing the current value of the time-to- + // expiry timer. + // * A RESERVATION-TOKEN attribute (if a second relayed transport + // address was reserved). + // * An XOR-MAPPED-ADDRESS attribute containing the client's IP address + // and port (from the 5-tuple). + + srcIP, srcPort, err := ipnet.AddrIPPort(r.SrcAddr) + if err != nil { + return buildAndSendErr(r.Conn, r.SrcAddr, err, badRequestMsg...) + } + + relayIP, relayPort, err := ipnet.AddrIPPort(a.RelayAddr) + if err != nil { + return buildAndSendErr(r.Conn, r.SrcAddr, err, badRequestMsg...) + } + + responseAttrs := []stun.Setter{ + &proto.RelayedAddress{ + IP: relayIP, + Port: relayPort, + }, + &proto.Lifetime{ + Duration: lifetimeDuration, + }, + &stun.XORMappedAddress{ + IP: srcIP, + Port: srcPort, + }, + } + + if reservationToken != "" { + r.AllocationManager.CreateReservation(reservationToken, relayPort) + responseAttrs = append(responseAttrs, proto.ReservationToken([]byte(reservationToken))) + } + + msg := buildMsg(m.TransactionID, stun.NewType(stun.MethodAllocate, stun.ClassSuccessResponse), append(responseAttrs, messageIntegrity)...) + return buildAndSend(r.Conn, r.SrcAddr, msg...) +} + +func handleRefreshRequest(r Request, m *stun.Message) error { + r.Log.Debugf("received RefreshRequest from %s", r.SrcAddr.String()) + + messageIntegrity, hasAuth, err := authenticateRequest(r, m, stun.MethodRefresh) + if !hasAuth { + return err + } + + lifetimeDuration := allocationLifeTime(m) + fiveTuple := &allocation.FiveTuple{ + SrcAddr: r.SrcAddr, + DstAddr: r.Conn.LocalAddr(), + Protocol: allocation.UDP, + } + + if lifetimeDuration != 0 { + a := r.AllocationManager.GetAllocation(fiveTuple) + + if a == nil { + return fmt.Errorf("%w %v:%v", errNoAllocationFound, r.SrcAddr, r.Conn.LocalAddr()) + } + a.Refresh(lifetimeDuration) + } else { + r.AllocationManager.DeleteAllocation(fiveTuple) + } + + return buildAndSend(r.Conn, r.SrcAddr, buildMsg(m.TransactionID, stun.NewType(stun.MethodRefresh, stun.ClassSuccessResponse), []stun.Setter{ + &proto.Lifetime{ + Duration: lifetimeDuration, + }, + messageIntegrity, + }...)...) +} + +func handleCreatePermissionRequest(r Request, m *stun.Message) error { + r.Log.Debugf("received CreatePermission from %s", r.SrcAddr.String()) + + a := r.AllocationManager.GetAllocation(&allocation.FiveTuple{ + SrcAddr: r.SrcAddr, + DstAddr: r.Conn.LocalAddr(), + Protocol: allocation.UDP, + }) + if a == nil { + return fmt.Errorf("%w %v:%v", errNoAllocationFound, r.SrcAddr, r.Conn.LocalAddr()) + } + + messageIntegrity, hasAuth, err := authenticateRequest(r, m, stun.MethodCreatePermission) + if !hasAuth { + return err + } + + addCount := 0 + + if err := m.ForEach(stun.AttrXORPeerAddress, func(m *stun.Message) error { + var peerAddress proto.PeerAddress + if err := peerAddress.GetFrom(m); err != nil { + return err + } + + r.Log.Debugf("adding permission for %s", fmt.Sprintf("%s:%d", + peerAddress.IP.String(), peerAddress.Port)) + a.AddPermission(allocation.NewPermission( + &net.UDPAddr{ + IP: peerAddress.IP, + Port: peerAddress.Port, + }, + r.Log, + )) + addCount++ + return nil + }); err != nil { + addCount = 0 + } + + respClass := stun.ClassSuccessResponse + if addCount == 0 { + respClass = stun.ClassErrorResponse + } + + return buildAndSend(r.Conn, r.SrcAddr, buildMsg(m.TransactionID, stun.NewType(stun.MethodCreatePermission, respClass), []stun.Setter{messageIntegrity}...)...) +} + +func handleSendIndication(r Request, m *stun.Message) error { + r.Log.Debugf("received SendIndication from %s", r.SrcAddr.String()) + a := r.AllocationManager.GetAllocation(&allocation.FiveTuple{ + SrcAddr: r.SrcAddr, + DstAddr: r.Conn.LocalAddr(), + Protocol: allocation.UDP, + }) + if a == nil { + return fmt.Errorf("%w %v:%v", errNoAllocationFound, r.SrcAddr, r.Conn.LocalAddr()) + } + + dataAttr := proto.Data{} + if err := dataAttr.GetFrom(m); err != nil { + return err + } + + peerAddress := proto.PeerAddress{} + if err := peerAddress.GetFrom(m); err != nil { + return err + } + + msgDst := &net.UDPAddr{IP: peerAddress.IP, Port: peerAddress.Port} + if perm := a.GetPermission(msgDst); perm == nil { + return fmt.Errorf("%w: %v", errNoPermission, msgDst) + } + + l, err := a.RelaySocket.WriteTo(dataAttr, msgDst) + if l != len(dataAttr) { + return fmt.Errorf("%w %d != %d (expected) err: %v", errShortWrite, l, len(dataAttr), err) + } + return err +} + +func handleChannelBindRequest(r Request, m *stun.Message) error { + r.Log.Debugf("received ChannelBindRequest from %s", r.SrcAddr.String()) + + a := r.AllocationManager.GetAllocation(&allocation.FiveTuple{ + SrcAddr: r.SrcAddr, + DstAddr: r.Conn.LocalAddr(), + Protocol: allocation.UDP, + }) + if a == nil { + return fmt.Errorf("%w %v:%v", errNoAllocationFound, r.SrcAddr, r.Conn.LocalAddr()) + } + + badRequestMsg := buildMsg(m.TransactionID, stun.NewType(stun.MethodChannelBind, stun.ClassErrorResponse), &stun.ErrorCodeAttribute{Code: stun.CodeBadRequest}) + + messageIntegrity, hasAuth, err := authenticateRequest(r, m, stun.MethodChannelBind) + if !hasAuth { + return err + } + + var channel proto.ChannelNumber + if err = channel.GetFrom(m); err != nil { + return buildAndSendErr(r.Conn, r.SrcAddr, err, badRequestMsg...) + } + + peerAddr := proto.PeerAddress{} + if err = peerAddr.GetFrom(m); err != nil { + return buildAndSendErr(r.Conn, r.SrcAddr, err, badRequestMsg...) + } + + r.Log.Debugf("binding channel %d to %s", + channel, + fmt.Sprintf("%s:%d", peerAddr.IP.String(), peerAddr.Port)) + err = a.AddChannelBind(allocation.NewChannelBind( + channel, + &net.UDPAddr{IP: peerAddr.IP, Port: peerAddr.Port}, + r.Log, + ), r.ChannelBindTimeout) + if err != nil { + return buildAndSendErr(r.Conn, r.SrcAddr, err, badRequestMsg...) + } + + return buildAndSend(r.Conn, r.SrcAddr, buildMsg(m.TransactionID, stun.NewType(stun.MethodChannelBind, stun.ClassSuccessResponse), []stun.Setter{messageIntegrity}...)...) +} + +func handleChannelData(r Request, c *proto.ChannelData) error { + r.Log.Debugf("received ChannelData from %s", r.SrcAddr.String()) + + a := r.AllocationManager.GetAllocation(&allocation.FiveTuple{ + SrcAddr: r.SrcAddr, + DstAddr: r.Conn.LocalAddr(), + Protocol: allocation.UDP, + }) + if a == nil { + return fmt.Errorf("%w %v:%v", errNoAllocationFound, r.SrcAddr, r.Conn.LocalAddr()) + } + + channel := a.GetChannelByNumber(c.Number) + if channel == nil { + return fmt.Errorf("%w %x", errNoSuchChannelBind, uint16(c.Number)) + } + + l, err := a.RelaySocket.WriteTo(c.Data, channel.Peer) + if err != nil { + return fmt.Errorf("%w: %s", errFailedWriteSocket, err.Error()) + } else if l != len(c.Data) { + return fmt.Errorf("%w %d != %d (expected)", errShortWrite, l, len(c.Data)) + } + + return nil +} |