diff options
Diffstat (limited to 'src/stun/stun_codec.erl')
-rw-r--r-- | src/stun/stun_codec.erl | 343 |
1 files changed, 343 insertions, 0 deletions
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 <ekhramtsov@process-one.net> +%%% Description : STUN codec +%%% Created : 7 Aug 2009 by Evgeniy Khramtsov <ekhramtsov@process-one.net> +%%% +%%% +%%% 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(<<Type:16, Len:16, Rest/binary>>, Msg) -> + PaddLen = padd_len(Len), + <<Val:Len/binary, _:PaddLen, Tail/binary>> = 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>>) -> + {A1, A2, A3, A4}; +dec_addr(2, <<A1:16, A2:16, A3:16, A4:16, + A5:16, A6:16, A7:16, A8:16>>) -> + {A1, A2, A3, A4, A5, A6, A7, A8}. + +dec_xor_addr(1, Magic, _TrID, <<XAddr:32>>) -> + Addr = XAddr bxor Magic, + dec_addr(1, <<Addr:32>>); +dec_xor_addr(2, Magic, TrID, <<XAddr:128>>) -> + Addr = XAddr bxor ((Magic bsl 96) bor TrID), + dec_addr(2, <<Addr:128>>). + +dec_unknown_attrs(<<Attr:16, Tail/binary>>, 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), + <<Attr:16, Len:16, Val/binary, 0:PaddLen>>. + +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), + <<Addr:32>> = <<A1, A2, A3, A4>>, + 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), + <<Addr:128>> = <<A1:16, A2:16, A3:16, A4:16, + A5:16, A6:16, A7:16, A8: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:16>> || 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()). |