From 0a8bcf6530277fd2ad67d652964bb8757ae0972a Mon Sep 17 00:00:00 2001 From: Evgeniy Khramtsov Date: Tue, 11 Aug 2009 12:54:40 +0000 Subject: [PATCH] restore missing stun directory SVN Revision: 2458 --- src/stun/Makefile.in | 38 ++++ src/stun/Makefile.win32 | 18 ++ src/stun/ejabberd_stun.erl | 260 ++++++++++++++++++++++++++++ src/stun/stun.hrl | 78 +++++++++ src/stun/stun_codec.erl | 343 +++++++++++++++++++++++++++++++++++++ 5 files changed, 737 insertions(+) create mode 100644 src/stun/Makefile.in create mode 100644 src/stun/Makefile.win32 create mode 100644 src/stun/ejabberd_stun.erl create mode 100644 src/stun/stun.hrl create mode 100644 src/stun/stun_codec.erl diff --git a/src/stun/Makefile.in b/src/stun/Makefile.in new file mode 100644 index 000000000..e77da8452 --- /dev/null +++ b/src/stun/Makefile.in @@ -0,0 +1,38 @@ +# $Id: Makefile.in 1453 2008-07-16 16:58:42Z badlop $ + +CC = @CC@ +CFLAGS = @CFLAGS@ +CPPFLAGS = @CPPFLAGS@ +LDFLAGS = @LDFLAGS@ +LIBS = @LIBS@ + +ERLANG_CFLAGS = @ERLANG_CFLAGS@ +ERLANG_LIBS = @ERLANG_LIBS@ + +EFLAGS += -I .. +EFLAGS += -pz .. + +# make debug=true to compile Erlang module with debug informations. +ifdef debug + EFLAGS+=+debug_info +endif + +OUTDIR = .. +SOURCES = $(wildcard *.erl) +BEAMS = $(addprefix $(OUTDIR)/,$(SOURCES:.erl=.beam)) + + +all: $(BEAMS) + +$(OUTDIR)/%.beam: %.erl + @ERLC@ -W $(EFLAGS) -o $(OUTDIR) $< + +clean: + rm -f $(BEAMS) + +distclean: clean + rm -f Makefile + +TAGS: + etags *.erl + diff --git a/src/stun/Makefile.win32 b/src/stun/Makefile.win32 new file mode 100644 index 000000000..e70aba9f1 --- /dev/null +++ b/src/stun/Makefile.win32 @@ -0,0 +1,18 @@ + +include ..\Makefile.inc + +EFLAGS = -I .. -pz .. + +OUTDIR = .. +BEAMS = ..\stun_codec.beam ..\ejabberd_stun.beam + +ALL : $(BEAMS) + +CLEAN : + -@erase $(BEAMS) + +$(OUTDIR)\stun_codec.beam : stun_codec.erl + erlc -W $(EFLAGS) -o $(OUTDIR) stun_codec.erl + +$(OUTDIR)\ejabberd_stun.beam : ejabberd_stun.erl + erlc -W $(EFLAGS) -o $(OUTDIR) ejabberd_stun.erl diff --git a/src/stun/ejabberd_stun.erl b/src/stun/ejabberd_stun.erl new file mode 100644 index 000000000..d5085821f --- /dev/null +++ b/src/stun/ejabberd_stun.erl @@ -0,0 +1,260 @@ +%%%------------------------------------------------------------------- +%%% File : ejabberd_stun.erl +%%% Author : Evgeniy Khramtsov +%%% Description : RFC5389 implementation. +%%% Currently only Binding usage is supported. +%%% +%%% Created : 8 Aug 2009 by Evgeniy Khramtsov +%%% +%%% +%%% ejabberd, Copyright (C) 2002-2009 ProcessOne +%%% +%%% This program is free software; you can redistribute it and/or +%%% modify it under the terms of the GNU General Public License as +%%% published by the Free Software Foundation; either version 2 of the +%%% License, or (at your option) any later version. +%%% +%%% This program is distributed in the hope that it will be useful, +%%% but WITHOUT ANY WARRANTY; without even the implied warranty of +%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +%%% General Public License for more details. +%%% +%%% You should have received a copy of the GNU General Public License +%%% along with this program; if not, write to the Free Software +%%% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA +%%% 02111-1307 USA +%%% +%%%------------------------------------------------------------------- +-module(ejabberd_stun). + +-behaviour(gen_fsm). + +%% API +-export([start_link/2, + start/2, + socket_type/0, + udp_recv/5]). + +%% gen_fsm callbacks +-export([init/1, + handle_event/3, + handle_sync_event/4, + handle_info/3, + terminate/3, + code_change/4]). + +%% gen_fsm states +-export([wait_for_tls/2, + session_established/2]). + +-include("ejabberd.hrl"). +-include("stun.hrl"). + +-define(MAX_BUF_SIZE, 64*1024). %% 64kb +-define(TIMEOUT, 10000). %% 10 sec + +-record(state, {sock, + sock_mod = gen_tcp, + certfile, + peer, + tref, + buf = <<>>}). + +%%==================================================================== +%% API +%%==================================================================== +start({gen_tcp, Sock}, Opts) -> + supervisor:start_child(ejabberd_stun_sup, [Sock, Opts]). + +start_link(Sock, Opts) -> + gen_fsm:start_link(?MODULE, [Sock, Opts], []). + +socket_type() -> + raw. + +udp_recv(Sock, Addr, Port, Data, _Opts) -> + case stun_codec:decode(Data) of + {ok, Msg, <<>>} -> + ?DEBUG("got:~n~s", [stun_codec:pp(Msg)]), + case process(Addr, Port, Msg) of + RespMsg when is_record(RespMsg, stun) -> + ?DEBUG("sent:~n~s", [stun_codec:pp(RespMsg)]), + Data1 = stun_codec:encode(RespMsg), + gen_udp:send(Sock, Addr, Port, Data1); + _ -> + ok + end; + _ -> + ok + end. + +%%==================================================================== +%% gen_fsm callbacks +%%==================================================================== +init([Sock, Opts]) -> + case inet:peername(Sock) of + {ok, Addr} -> + inet:setopts(Sock, [{active, once}]), + TRef = erlang:start_timer(?TIMEOUT, self(), stop), + State = #state{sock = Sock, peer = Addr, tref = TRef}, + case proplists:get_value(certfile, Opts) of + undefined -> + {ok, session_established, State}; + CertFile -> + {ok, wait_for_tls, State#state{certfile = CertFile}} + end; + Err -> + Err + end. + +wait_for_tls(Event, State) -> + ?INFO_MSG("unexpected event in wait_for_tls: ~p", [Event]), + {next_state, wait_for_tls, State}. + +session_established(Msg, State) when is_record(Msg, stun) -> + ?DEBUG("got:~n~s", [stun_codec:pp(Msg)]), + {Addr, Port} = State#state.peer, + case process(Addr, Port, Msg) of + Resp when is_record(Resp, stun) -> + ?DEBUG("sent:~n~s", [stun_codec:pp(Resp)]), + Data = stun_codec:encode(Resp), + (State#state.sock_mod):send(State#state.sock, Data); + _ -> + ok + end, + {next_state, session_established, State}; +session_established(Event, State) -> + ?INFO_MSG("unexpected event in session_established: ~p", [Event]), + {next_state, session_established, State}. + +handle_event(_Event, StateName, State) -> + {next_state, StateName, State}. + +handle_sync_event(_Event, _From, StateName, State) -> + {reply, {error, badarg}, StateName, State}. + +handle_info({tcp, Sock, TLSData}, wait_for_tls, State) -> + Buf = <<(State#state.buf)/binary, TLSData/binary>>, + %% Check if the initial message is a TLS handshake + case Buf of + _ when size(Buf) < 3 -> + {next_state, wait_for_tls, + update_state(State#state{buf = Buf})}; + <<_:16, 1, _/binary>> -> + TLSOpts = [{certfile, State#state.certfile}], + {ok, TLSSock} = tls:tcp_to_tls(Sock, TLSOpts), + NewState = State#state{sock = TLSSock, + buf = <<>>, + sock_mod = tls}, + case tls:recv_data(TLSSock, Buf) of + {ok, Data} -> + process_data(session_established, NewState, Data); + _Err -> + {stop, normal, NewState} + end; + _ -> + process_data(session_established, State, TLSData) + end; +handle_info({tcp, _Sock, TLSData}, StateName, + #state{sock_mod = tls} = State) -> + case tls:recv_data(State#state.sock, TLSData) of + {ok, Data} -> + process_data(StateName, State, Data); + _Err -> + {stop, normal, State} + end; +handle_info({tcp, _Sock, Data}, StateName, State) -> + process_data(StateName, State, Data); +handle_info({tcp_closed, _Sock}, _StateName, State) -> + ?DEBUG("connection reset by peer", []), + {stop, normal, State}; +handle_info({tcp_error, _Sock, Reason}, _StateName, State) -> + ?DEBUG("connection error: ~p", [Reason]), + {stop, normal, State}; +handle_info({timeout, TRef, stop}, _StateName, + #state{tref = TRef} = State) -> + {stop, normal, State}; +handle_info(Info, StateName, State) -> + ?INFO_MSG("unexpected info: ~p", [Info]), + {next_state, StateName, State}. + +terminate(_Reason, _StateName, State) -> + catch (State#state.sock_mod):close(State#state.sock), + ok. + +code_change(_OldVsn, StateName, State, _Extra) -> + {ok, StateName, State}. + +%%-------------------------------------------------------------------- +%%% Internal functions +%%-------------------------------------------------------------------- +process(Addr, Port, #stun{class = request, unsupported = []} = Msg) -> + Resp = prepare_response(Msg), + if Msg#stun.method == ?STUN_METHOD_BINDING -> + case stun_codec:version(Msg) of + old -> + Resp#stun{class = response, + 'MAPPED-ADDRESS' = {Addr, Port}}; + new -> + Resp#stun{class = response, + 'MAPPED-ADDRESS' = {Addr, Port}, + 'XOR-MAPPED-ADDRESS' = {Addr, Port}} + end; + true -> + Resp#stun{class = error, + 'ERROR-CODE' = {405, <<"Method Not Allowed">>}} + end; +process(_Addr, _Port, #stun{class = request} = Msg) -> + Resp = prepare_response(Msg), + Resp#stun{class = error, + 'UNKNOWN-ATTRIBUTES' = Msg#stun.unsupported, + 'ERROR-CODE' = {420, stun_codec:reason(420)}}; +process(_Addr, _Port, _Msg) -> + pass. + +prepare_response(Msg) -> + Version = list_to_binary("ejabberd " ++ ?VERSION), + #stun{method = Msg#stun.method, + magic = Msg#stun.magic, + trid = Msg#stun.trid, + 'SOFTWARE' = Version}. + +process_data(NextStateName, #state{buf = Buf} = State, Data) -> + NewBuf = <>, + case stun_codec:decode(NewBuf) of + {ok, Msg, Tail} -> + gen_fsm:send_event(self(), Msg), + process_data(NextStateName, State#state{buf = <<>>}, Tail); + empty -> + NewState = State#state{buf = <<>>}, + {next_state, NextStateName, update_state(NewState)}; + more when size(NewBuf) < ?MAX_BUF_SIZE -> + NewState = State#state{buf = NewBuf}, + {next_state, NextStateName, update_state(NewState)}; + _ -> + {stop, normal, State} + end. + +update_state(#state{sock = Sock} = State) -> + case State#state.sock_mod of + gen_tcp -> + inet:setopts(Sock, [{active, once}]); + SockMod -> + SockMod:setopts(Sock, [{active, once}]) + end, + cancel_timer(State#state.tref), + TRef = erlang:start_timer(?TIMEOUT, self(), stop), + State#state{tref = TRef}. + +cancel_timer(TRef) -> + case erlang:cancel_timer(TRef) of + false -> + receive + {timeout, TRef, _} -> + ok + after 0 -> + ok + end; + _ -> + ok + end. diff --git a/src/stun/stun.hrl b/src/stun/stun.hrl new file mode 100644 index 000000000..c22184449 --- /dev/null +++ b/src/stun/stun.hrl @@ -0,0 +1,78 @@ +%%%------------------------------------------------------------------- +%%% File : stun.hrl +%%% Author : Evgeniy Khramtsov +%%% Description : STUN values +%%% Created : 8 Aug 2009 by Evgeniy Khramtsov +%%% +%%% +%%% ejabberd, Copyright (C) 2002-2009 ProcessOne +%%% +%%% This program is free software; you can redistribute it and/or +%%% modify it under the terms of the GNU General Public License as +%%% published by the Free Software Foundation; either version 2 of the +%%% License, or (at your option) any later version. +%%% +%%% This program is distributed in the hope that it will be useful, +%%% but WITHOUT ANY WARRANTY; without even the implied warranty of +%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +%%% General Public License for more details. +%%% +%%% You should have received a copy of the GNU General Public License +%%% along with this program; if not, write to the Free Software +%%% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA +%%% 02111-1307 USA +%%% +%%%------------------------------------------------------------------- +-define(STUN_MAGIC, 16#2112a442). + +%% I know, this is terrible. Refer to 'STUN Message Structure' of +%% RFC5389 to understand this. +-define(STUN_METHOD(Type), + ((Type band 16#3e00) bsr 2) bor + ((Type band 16#e0) bsr 1) bor (Type band 16#f)). +-define(STUN_CLASS(Type), + ((Type band 16#100) bsr 7) bor + ((Type band 16#10) bsr 4)). +-define(STUN_TYPE(C, M), + (((M band 16#f80) bsl 2) + bor ((M band 16#70) bsl 1) + bor (M band 16#f) ) + bor (((C band 16#2) bsl 7) bor ((C band 16#1) bsl 4))). + +-define(is_required(A), (A =< 16#7fff)). + +-define(STUN_METHOD_BINDING, 16#001). + +%% Comprehension-required range (0x0000-0x7FFF) +-define(STUN_ATTR_MAPPED_ADDRESS, 16#0001). +-define(STUN_ATTR_USERNAME, 16#0006). +-define(STUN_ATTR_MESSAGE_INTEGRITY, 16#0008). +-define(STUN_ATTR_ERROR_CODE, 16#0009). +-define(STUN_ATTR_UNKNOWN_ATTRIBUTES, 16#000a). +-define(STUN_ATTR_REALM, 16#0014). +-define(STUN_ATTR_NONCE, 16#0015). +-define(STUN_ATTR_XOR_MAPPED_ADDRESS, 16#0020). + +%% Comprehension-optional range (0x8000-0xFFFF) +-define(STUN_ATTR_SOFTWARE, 16#8022). +-define(STUN_ATTR_ALTERNATE_SERVER, 16#8023). +-define(STUN_ATTR_FINGERPRINT, 16#8028). + +-record(stun, {class, + method, + magic = ?STUN_MAGIC, + trid, + unsupported = [], + 'SOFTWARE', + 'ALTERNATE-SERVER', + 'MAPPED-ADDRESS', + 'XOR-MAPPED-ADDRESS', + 'USERNAME', + 'REALM', + 'NONCE', + 'MESSAGE-INTEGRITY', + 'ERROR-CODE', + 'UNKNOWN-ATTRIBUTES' = []}). + +%% Workarounds. +%%-define(NO_PADDING, true). diff --git a/src/stun/stun_codec.erl b/src/stun/stun_codec.erl new file mode 100644 index 000000000..2539da996 --- /dev/null +++ b/src/stun/stun_codec.erl @@ -0,0 +1,343 @@ +%%%------------------------------------------------------------------- +%%% File : stun_codec.erl +%%% Author : Evgeniy Khramtsov +%%% Description : STUN codec +%%% Created : 7 Aug 2009 by Evgeniy Khramtsov +%%% +%%% +%%% ejabberd, Copyright (C) 2002-2009 ProcessOne +%%% +%%% This program is free software; you can redistribute it and/or +%%% modify it under the terms of the GNU General Public License as +%%% published by the Free Software Foundation; either version 2 of the +%%% License, or (at your option) any later version. +%%% +%%% This program is distributed in the hope that it will be useful, +%%% but WITHOUT ANY WARRANTY; without even the implied warranty of +%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +%%% General Public License for more details. +%%% +%%% You should have received a copy of the GNU General Public License +%%% along with this program; if not, write to the Free Software +%%% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA +%%% 02111-1307 USA +%%% +%%%------------------------------------------------------------------- +-module(stun_codec). + +%% API +-export([decode/1, + encode/1, + version/1, + reason/1, + pp/1]). + +%% Tests +-export([test_udp/2, + test_tcp/2, + test_tls/2, + test_public/0]). + +-include("stun.hrl"). + +%%==================================================================== +%% API +%%==================================================================== +decode(<<0:2, Type:14, Len:16, Magic:32, TrID:96, + Body:Len/binary, Tail/binary>>) -> + case catch decode(Type, Magic, TrID, Body) of + {'EXIT', _} -> + {error, unparsed}; + Res -> + {ok, Res, Tail} + end; +decode(<<0:2, _/binary>>) -> + more; +decode(<<>>) -> + empty; +decode(_) -> + {error, unparsed}. + +encode(#stun{class = Class, + method = Method, + magic = Magic, + trid = TrID} = Msg) -> + ClassCode = case Class of + request -> 0; + indication -> 1; + response -> 2; + error -> 3 + end, + Type = ?STUN_TYPE(ClassCode, Method), + Attrs = enc_attrs(Msg), + Len = size(Attrs), + <<0:2, Type:14, Len:16, Magic:32, TrID:96, Attrs/binary>>. + +pp(Term) -> + io_lib_pretty:print(Term, fun pp/2). + +version(#stun{magic = ?STUN_MAGIC}) -> + new; +version(#stun{}) -> + old. + +reason(300) -> <<"Try Alternate">>; +reason(400) -> <<"Bad Request">>; +reason(401) -> <<"Unauthorized">>; +reason(420) -> <<"Unknown Attribute">>; +reason(438) -> <<"Stale Nonce">>; +reason(500) -> <<"Server Error">>; +reason(_) -> <<"Undefined Error">>. + +%%==================================================================== +%% Internal functions +%%==================================================================== +decode(Type, Magic, TrID, Body) -> + Method = ?STUN_METHOD(Type), + Class = case ?STUN_CLASS(Type) of + 0 -> request; + 1 -> indication; + 2 -> response; + 3 -> error + end, + dec_attrs(Body, #stun{class = Class, + method = Method, + magic = Magic, + trid = TrID}). + +dec_attrs(<>, Msg) -> + PaddLen = padd_len(Len), + <> = Rest, + NewMsg = dec_attr(Type, Val, Msg), + if Type == ?STUN_ATTR_MESSAGE_INTEGRITY -> + NewMsg; + true -> + dec_attrs(Tail, NewMsg) + end; +dec_attrs(<<>>, Msg) -> + Msg. + +enc_attrs(Msg) -> + concat_binary( + [enc_attr(?STUN_ATTR_SOFTWARE, Msg#stun.'SOFTWARE'), + enc_addr(?STUN_ATTR_MAPPED_ADDRESS, Msg#stun.'MAPPED-ADDRESS'), + enc_xor_addr(?STUN_ATTR_XOR_MAPPED_ADDRESS, + Msg#stun.magic, Msg#stun.trid, + Msg#stun.'XOR-MAPPED-ADDRESS'), + enc_addr(?STUN_ATTR_ALTERNATE_SERVER, Msg#stun.'ALTERNATE-SERVER'), + enc_attr(?STUN_ATTR_USERNAME, Msg#stun.'USERNAME'), + enc_attr(?STUN_ATTR_REALM, Msg#stun.'REALM'), + enc_attr(?STUN_ATTR_NONCE, Msg#stun.'NONCE'), + enc_error_code(Msg#stun.'ERROR-CODE'), + enc_unknown_attrs(Msg#stun.'UNKNOWN-ATTRIBUTES')]). + +dec_attr(?STUN_ATTR_MAPPED_ADDRESS, Val, Msg) -> + <<_, Family, Port:16, AddrBin/binary>> = Val, + Addr = dec_addr(Family, AddrBin), + Msg#stun{'MAPPED-ADDRESS' = {Addr, Port}}; +dec_attr(?STUN_ATTR_XOR_MAPPED_ADDRESS, Val, Msg) -> + <<_, Family, XPort:16, XAddr/binary>> = Val, + Magic = Msg#stun.magic, + Port = XPort bxor (Magic bsr 16), + Addr = dec_xor_addr(Family, Magic, Msg#stun.trid, XAddr), + Msg#stun{'XOR-MAPPED-ADDRESS' = {Addr, Port}}; +dec_attr(?STUN_ATTR_SOFTWARE, Val, Msg) -> + Msg#stun{'SOFTWARE' = Val}; +dec_attr(?STUN_ATTR_USERNAME, Val, Msg) -> + Msg#stun{'USERNAME' = Val}; +dec_attr(?STUN_ATTR_REALM, Val, Msg) -> + Msg#stun{'REALM' = Val}; +dec_attr(?STUN_ATTR_NONCE, Val, Msg) -> + Msg#stun{'NONCE' = Val}; +dec_attr(?STUN_ATTR_MESSAGE_INTEGRITY, Val, Msg) -> + Msg#stun{'MESSAGE-INTEGRITY' = Val}; +dec_attr(?STUN_ATTR_ALTERNATE_SERVER, Val, Msg) -> + <<_, Family, Port:16, Address/binary>> = Val, + IP = dec_addr(Family, Address), + Msg#stun{'ALTERNATE-SERVER' = {IP, Port}}; +dec_attr(?STUN_ATTR_ERROR_CODE, Val, Msg) -> + <<_:21, Class:3, Number:8, Reason/binary>> = Val, + if Class >=3, Class =< 6, Number >=0, Number =< 99 -> + Code = Class * 100 + Number, + Msg#stun{'ERROR-CODE' = {Code, Reason}} + end; +dec_attr(?STUN_ATTR_UNKNOWN_ATTRIBUTES, Val, Msg) -> + Attrs = dec_unknown_attrs(Val, []), + Msg#stun{'UNKNOWN-ATTRIBUTES' = Attrs}; +dec_attr(Attr, _Val, #stun{unsupported = Attrs} = Msg) + when Attr =< 16#7fff -> + Msg#stun{unsupported = [Attr|Attrs]}; +dec_attr(_Attr, _Val, Msg) -> + Msg. + +dec_addr(1, <>) -> + {A1, A2, A3, A4}; +dec_addr(2, <>) -> + {A1, A2, A3, A4, A5, A6, A7, A8}. + +dec_xor_addr(1, Magic, _TrID, <>) -> + Addr = XAddr bxor Magic, + dec_addr(1, <>); +dec_xor_addr(2, Magic, TrID, <>) -> + Addr = XAddr bxor ((Magic bsl 96) bor TrID), + dec_addr(2, <>). + +dec_unknown_attrs(<>, Acc) -> + dec_unknown_attrs(Tail, [Attr|Acc]); +dec_unknown_attrs(<<>>, Acc) -> + lists:reverse(Acc). + +enc_attr(_Attr, undefined) -> + <<>>; +enc_attr(Attr, Val) -> + Len = size(Val), + PaddLen = padd_len(Len), + <>. + +enc_addr(_Type, undefined) -> + <<>>; +enc_addr(Type, {{A1, A2, A3, A4}, Port}) -> + enc_attr(Type, <<0, 1, Port:16, A1, A2, A3, A4>>); +enc_addr(Type, {{A1, A2, A3, A4, A5, A6, A7, A8}, Port}) -> + enc_attr(Type, <<0, 2, Port:16, A1:16, A2:16, A3:16, + A4:16, A5:16, A6:16, A7:16, A8:16>>). + +enc_xor_addr(_Type, _Magic, _TrID, undefined) -> + <<>>; +enc_xor_addr(Type, Magic, _TrID, {{A1, A2, A3, A4}, Port}) -> + XPort = Port bxor (Magic bsr 16), + <> = <>, + XAddr = Addr bxor Magic, + enc_attr(Type, <<0, 1, XPort:16, XAddr:32>>); +enc_xor_addr(Type, Magic, TrID, + {{A1, A2, A3, A4, A5, A6, A7, A8}, Port}) -> + XPort = Port bxor (Magic bsr 16), + <> = <>, + XAddr = Addr bxor ((Magic bsl 96) bor TrID), + enc_attr(Type, <<0, 2, XPort:16, XAddr:128>>). + +enc_error_code(undefined) -> + <<>>; +enc_error_code({Code, Reason}) -> + Class = Code div 100, + Number = Code rem 100, + enc_attr(?STUN_ATTR_ERROR_CODE, + <<0:21, Class:3, Number:8, Reason/binary>>). + +enc_unknown_attrs([]) -> + <<>>; +enc_unknown_attrs(Attrs) -> + enc_attr(?STUN_ATTR_UNKNOWN_ATTRIBUTES, + concat_binary([<> || Attr <- Attrs])). + +%%==================================================================== +%% Auxiliary functions +%%==================================================================== +pp(Tag, N) -> + try + pp1(Tag, N) + catch _:_ -> + no + end. + +pp1(stun, N) -> + N = record_info(size, stun) - 1, + record_info(fields, stun); +pp1(_, _) -> + no. + +%% Workaround for stupid clients. +-ifdef(NO_PADDING). +padd_len(_Len) -> + 0. +-else. +padd_len(Len) -> + case Len rem 4 of + 0 -> 0; + N -> 8*(4-N) + end. +-endif. + +%%==================================================================== +%% Test functions +%%==================================================================== +bind_msg() -> + Msg = #stun{method = ?STUN_METHOD_BINDING, + class = request, + trid = random:uniform(1 bsl 96), + 'SOFTWARE' = <<"test">>}, + encode(Msg). + +test_udp(Addr, Port) -> + test(Addr, Port, gen_udp). + +test_tcp(Addr, Port) -> + test(Addr, Port, gen_tcp). + +test_tls(Addr, Port) -> + test(Addr, Port, ssl). + +test(Addr, Port, Mod) -> + Res = case Mod of + gen_udp -> + Mod:open(0, [binary, {active, false}]); + _ -> + Mod:connect(Addr, Port, + [binary, {active, false}], 1000) + end, + case Res of + {ok, Sock} -> + if Mod == gen_udp -> + Mod:send(Sock, Addr, Port, bind_msg()); + true -> + Mod:send(Sock, bind_msg()) + end, + case Mod:recv(Sock, 0, 1000) of + {ok, {_, _, Data}} -> + try_dec(Data); + {ok, Data} -> + try_dec(Data); + Err -> + io:format("err: ~p~n", [Err]) + end, + Mod:close(Sock); + Err -> + io:format("err: ~p~n", [Err]) + end. + +try_dec(Data) -> + case decode(Data) of + {ok, Msg, _} -> + io:format("got:~n~s~n", [pp(Msg)]); + Err -> + io:format("err: ~p~n", [Err]) + end. + +public_servers() -> + [{"stun.ekiga.net", 3478, 3478, 5349}, + {"stun.fwdnet.net", 3478, 3478, 5349}, + {"stun.ideasip.com", 3478, 3478, 5349}, + {"stun01.sipphone.com", 3478, 3478, 5349}, + {"stun.softjoys.com", 3478, 3478, 5349}, + {"stun.voipbuster.com", 3478, 3478, 5349}, + {"stun.voxgratia.org", 3478, 3478, 5349}, + {"stun.xten.com", 3478, 3478, 5349}, + {"stunserver.org", 3478, 3478, 5349}, + {"stun.sipgate.net", 10000, 10000, 5349}, + {"numb.viagenie.ca", 3478, 3478, 5349}, + {"stun.ipshka.com", 3478, 3478, 5349}, + {"localhost", 3478, 5349, 5349}]. + +test_public() -> + ssl:start(), + lists:foreach( + fun({Addr, UDPPort, TCPPort, TLSPort}) -> + io:format("trying ~s:~p on UDP... ", [Addr, UDPPort]), + test_udp(Addr, UDPPort), + io:format("trying ~s:~p on TCP... ", [Addr, TCPPort]), + test_tcp(Addr, TCPPort), + io:format("trying ~s:~p on TLS... ", [Addr, TLSPort]), + test_tls(Addr, TLSPort) + end, public_servers()).