Warning: strpos() [function.strpos]: Empty needle in /home/dealer/kasat/pkg/cahir/web/html/lib/plugins/translation2/action.php on line 53

Warning: Cannot modify header information - headers already sent by (output started at /home/dealer/kasat/pkg/cahir/web/html/lib/plugins/translation2/action.php:53) in /home/dealer/kasat/pkg/cahir/web/html/inc/actions.php on line 163
erlang:sockets [Krystian Bacławski Wiki]
 

Gniazdka TCP/IP

Protokół IP

Ramka protokołu IP

+ 0 - 3 4 - 7 8 - 15 16 - 18 19 - 31
0 Wersja Długość nagłówka Typ usługi (ToS) Całkowita długość
32 Numer identyfikacyjny Flagi Kontrola przesunięcia
64 Czas życia pakietu (TTL) Protokół warstwy wyższej Suma kontrolna nagłówka
96 Adres źródłowy IP
128 Adres docelowy IP
160 Opcje IP Uzupełnienie
192 Dane

Przy fragmentacji i defragmentacji biorą udział:

  • numer identyfikacyjny,
  • flagi,
  • kontrola przesunięcia.

Podstawowe informacje

Proces kontrolujący

Jeśli gniazdko znajduje się w stanie aktywnym to wysyła do procesu kontrolującego komunikaty. W przeciwnym wypadku proces sam musi odbierać dane i badać status gniazdka.

Domyślnym procesem kontrolującym gniazdko jest proces, który je utworzył.

Błędy

Wszystkie błędy pochodzące z obsługi sieci odpowiadają numerom błedów errno. Można je konwertować do postaci czytelnej dla człowieka:

inet:format_error(Posix) -> string()

Informacje o konfiguracji hosta

> inet:get_rc().
  [{domain,"ps3.prac.ii"},
   {search,["ps3.prac.ii"]},
   {resolv_conf,"/etc/resolv.conf"},
   {hosts_file,"/etc/hosts"},
   {lookup,[native]}]

Typy danych

Opis hosta

#hostent{h_addr_list = [ip_address()],
         h_addrtype  = inet | inet6,
         h_aliases = [hostname()],
         h_length = int(),
         h_name = hostname() }

Jest przechowywany w pliku:

-include_lib("kernel/include/inet.hrl").

Adres IP

Adres IP jest reprezentowany w Erlangu jako krotka:

1> inet_parse:address("192.168.42.2").
{ok,{192,168,42,2}}
2> inet_parse:address("FFFF::192.168.42.2").
{ok,{65535,0,0,0,0,0,49320,10754}}

Rozwiązywanie nazw DNS

Do pobrania adresu / adresów dowolnego komputera w Internecie służą:

inet:getaddr(Host, Family) -> {ok, Address} | {error, posix()}
inet:getaddrs(Host, Family) -> {ok, Addresses} | {error, posix()}
inet:gethostbyaddr(Address) -> {ok, Hostent} | {error, posix()}
inet:gethostbyname(Name) -> {ok, Hostent} | {error, posix()}
inet:gethostbyname(Name, Family) -> {ok, Hostent} | {error, posix()}

Własny adres możemy pobrać przy pomocy:

inet:gethostname() -> {ok, Hostname}

Przykłady

> inet:getaddr("www.ii.uni.wroc.pl",inet).
{ok,{156,17,4,3}}
> inet:getaddrs("www.google.com",inet).
{ok,[{74,125,39,106},
     {74,125,39,147},
     {74,125,39,99},
     {74,125,39,103},
     {74,125,39,104},
     {74,125,39,105}]}
> inet:gethostbyaddr({156,17,4,1}).
{ok,{hostent,"swiatowit.ii.uni.wroc.pl",[],inet,4,[{156,17,4,1}]}}
> inet:gethostbyname("www.google.com").
{ok,{hostent,"www.l.google.com",
             ["www.google.com"],
             inet,4,
             [{74,125,39,104},
              {74,125,39,105},
              {74,125,39,106},
              {74,125,39,147},
              {74,125,39,99},
              {74,125,39,103}]}}

Wbudowane dekodowanie pakietów

Obsługa parsera

erlang:decode_packet(Type,Bin,Options) -> {ok,Packet,Rest} | {more,Length} | {error,Reason}

Typy:

  • raw lub 0 – bez interpretacji pakietu,
  • 1 lub 2 lub 4 – pakiet posiada nagłówek w postaci 1, 2 lub 4 bajtów przechowujących jedną liczbę całkowitą w formacie big-endian, liczba ta określa długość danych; naŋłówek zostanie odcięty przed zwróceniem pakietu,
  • line – pakiet jest linią zakończoną znakiem końca linii; patrz opcja line_length,
  • protokoły binarne, nagłówek nie zostanie odcięty:
    • asn1 - ASN.1 BER
    • sunrm - Sun's RPC
    • cdr - CORBA (GIOP 1.1)
    • fcgi - Fast CGI
    • tpkt - TPKT (RFC1006)
  • http lub httph lub http_bin lub httph_bin – interpretacja pakietów protokółu HTTP (żądanie, odpowiedź, nagłówek, znacznik końca nagłówków); wersje '_bin' zwracają ciągi binarne zamiast ciągów znakowych; rozpoznane części pakietu są przekształcane na atomy, inne zwracane jako ciągi.

Opcje:

  • {packet_size, int()} – maksymalny rozmiar pakietu, jeśli zostanie przekroczony parser uznaje pakiet za błędny i zwraca błąd (domyślnie brak - wartość 0),
  • {line_length, int()} – tylko dla prasera w trybie interpretacji linii (http i line), ograniczenie na długość linii - po przekroczeniu, kolejne znaki są ucinane.

Prosty przykład

> erlang:decode_packet(1,<<3,"abcd">>,[]).
{ok,<<"abc">>,<<"d">>}
> erlang:decode_packet(1,<<5,"abcd">>,[]).
{more,6}

Parsowanie pakietów HTTP

Pakiet protokołu HTTP jest podzielony na trzy części:

  • komenda (żądanie lub odpowiedź),
  • nagłówki,
  • ciało komunikatu (payload).

W związku z tym parsowanie pakietu trzeba podzielić na kilka faz:

  • odczytanie komendy - typ http lub http_bin,
  • odczytywanie nagłówków - typ httph lub httph_bin:
    • dopóki parser nie zwrócił http_eoh wczytaj kolejny nagłówek,
  • odczytywanie ciała komunikatu (tu metoda zależna od zawartości) - długość zależna od nagłówka Content-Length, lub pakiet przesyłany przy pomocy kodowania Chunked-Encoding.
HttpPacket =
  {http_request, HttpMethod, HttpUri, HttpVersion} |
  {http_response, HttpVersion, integer(), HttpString} |
  {http_header, int(), HttpField, Reserved=term(), Value=HttpString} |
  http_eoh |
  {http_error, HttpString}
 
HttpMethod = HttpMethodAtom | HttpString
HttpMethodAtom = 'OPTIONS' | 'GET' | 'HEAD' | 'POST' | 'PUT' | 'DELETE' | 'TRACE'
 
HttpUri =
  '*' |
  {absoluteURI, http | https, Host=HttpString, Port=int() | undefined, Path=HttpString} |
  {scheme, Scheme=HttpString, HttpString} |
  {abs_path, HttpString} |
  HttpString
 
HttpVersion = {Major=int(), Minor=int()}
 
HttpField = HttpFieldAtom | HttpString
HttpFieldAtom =
  'Cache-Control' | 'Connection' | 'Date' | 'Pragma' | 'Transfer-Encoding' | 'Upgrade' | 'Via' |
  'Accept' | 'Accept-Charset' | 'Accept-Encoding' | 'Accept-Language' | 'Authorization' | 'From' |
  'Host' | 'If-Modified-Since' | 'If-Match' | 'If-None-Match' | 'If-Range' | 'If-Unmodified-Since' |
  'Max-Forwards' | 'Proxy-Authorization' | 'Range' | 'Referer' | 'User-Agent' | 'Age' | 'Location' |
  'Proxy-Authenticate' | 'Public' | 'Retry-After' | 'Server' | 'Vary' | 'Warning' |
  'Www-Authenticate' | 'Allow' | 'Content-Base' | 'Content-Encoding' | 'Content-Language' |
  'Content-Length' | 'Content-Location' | 'Content-Md5' | 'Content-Range' | 'Content-Type' |
  'Etag' | 'Expires' | 'Last-Modified' | 'Accept-Ranges' | 'Set-Cookie' | 'Set-Cookie2' |
  'X-Forwarded-For' | 'Cookie' | 'Keep-Alive' | 'Proxy-Connection'

Obsługa gniazdek

Opcje gniazdka

Lista dostępnych opcji

-type socket_setopt() ::
      {'raw', non_neg_integer(), non_neg_integer(), binary()} |
      {'reuseaddr',       boolean()} |
      {'keepalive',       boolean()} |
      {'dontroute',       boolean()} |
      {'linger',          {boolean(), non_neg_integer()}} |
      {'broadcast',       boolean()} |
      {'sndbuf',          non_neg_integer()} |
      {'recbuf',          non_neg_integer()} |
      {'priority',        non_neg_integer()} |
      {'tos',             non_neg_integer()} |
      {'nodelay',         boolean()} |
      {'multicast_ttl',   non_neg_integer()} |
      {'multicast_loop',  boolean()} |
      {'multicast_if',    ip_address()} |
      {'add_membership',  {ip_address(), ip_address()}} |
      {'drop_membership', {ip_address(), ip_address()}} |
      {'header',          non_neg_integer()} |
      {'buffer',          non_neg_integer()} |
      {'active',          boolean() | 'once'} |
      {'packet',          0 | 1 | 2 | 4 | 'raw' | 'sunrm' |  'asn1' | 'cdr' | 'fcgi' | 'line' | 'tpkt' | 'http' | 'httph' | 'http_bin' | 'httph_bin' } |
      {'mode',            list() | binary()} |
      {'port',           'port', 'term'} |
      {'exit_on_close',   boolean()} |
      {'low_watermark',   non_neg_integer()} |
      {'high_watermark',  non_neg_integer()} |
      {'bit8',            'clear' | 'set' | 'on' | 'off'} |
      {'send_timeout',    non_neg_integer() | 'infinity'} |
      {'send_timeout_close', boolean()} |
      {'delay_send',      boolean()} |
      {'packet_size',     non_neg_integer()} |
      {'read_packets',    non_neg_integer()}}.

Tryb pracy gniazdka

{active, true | false | once}
  • true - dane są wysyłane z gniazdka do procesu kontrolującego przy pomocy komunikatu,
  • false - należy samemu odebrać dane przy pomocy funkcji recv/2 lub recv/3,
  • once - pierwszy pakiet jak przy active = true, następne jak przy active = false (analogia do edge-triggered).

Format danych

Dane mogą być pobierane z gniazdka w dwóch postaciach:

  • list - ciągu znaków,
  • binary - ciągu binarnego.

Przy wczytywaniu pakietu jako ciągu binarnego można podać opcję {header, Size}, aby wymusić odebranie pakietu, który jest długości co najmniej Size.

Buforowanie

  • {delay_send, Boolean} - włącza / wyłącza buforowanie na poziomie implementacji Erlanga
  • {recbuf, Integer} - długość bufora odbierania
  • {sndbuf, Integer} - długość bufora wysyłania

Co jeśli dane nie mieszczą się w buforze? W zależności od protokołu… albo zostaną przycięte (UDP), albo wysyłający będzie czekał (TCP).

Specyficzne dla TCP

  • {keepalive, Boolean} - wysyłaj zduplikowane ACK by podtrzymywać połączenie,
  • {nodelay, Boolean} - wyłącza buforowanie danych po stronie stosu TCP/IP,
  • {packet_size, Integer} - jeśli pakiet jest większy niż podana liczba, zostaje uznany za niepoprawny i odrzucony,
  • {send_timeout, Integer} - domyślny maksymalny czas wysyłania pakietu,
  • {send_timeout_close, Boolean} - czy gniazdko ma zostać zamknięte po osiągnięciu limitu czasowego na wysyłanie?
  • {port, Port} - lokalny port, z którego będzie nawiązane połączenie,
  • {packet, PacketType} - typ pakietu jak przy erlang:decode_packet/3.

Specyficzne dla UDP

  • {broadcast, Boolean} - zezwala na rozgłaszanie pakietów,
  • {read_packets, Integer} - ile pakietów należy buforować (domyślnie pięć sztuk) zanim gniazdko powiadomi o danych.

Pozostałe opcje

  • inet - gniazdko protokołu IPv4,
  • inet6 - gniazdko protokołu IPv6,
  • {ip, ip_address()} - wybiera interfejs, z którego będzie nawiązywane połączenie (w przypadku wielu interfejsów sieciowych),
  • {reuseaddr, Boolean} - zezwala na natychmiastowe ponowne użycie portu do nasłuchiwania.

Pobieranie i modyfikacja opcji gniazdka

Po utworzeniu możemy odczytać jak i zmodyfikować ustawienia gniazdka.

inet:getopts(Socket, Options) -> OptionValues | {error, posix()}
inet:setopts(Socket, Options) -> ok | {error, posix()}

Należy się używać / spodziewać się listy opcji możliwych dla danego typu gniazdka.

Zamykanie połączeń

Niezależnie od rodzaju gniazda, można je zamknąć przy pomocy:

inet:close(Socket) -> ok | {error, Reason}

Albo funkcjami w modułach protokołów:

gen_tcp:close(Socket) -> ok | {error, Reason}
gen_udp:close(Socket) -> ok | {error, Reason}

Informacje o gniazdkach

Statystyki gniazdka

Lista dostępnych statystyk:

> inet:stats().
  [recv_oct,recv_cnt,recv_max,recv_avg,recv_dvi,send_oct,
   send_cnt,send_max,send_avg,send_pend]

Pobieranie statystyk z gniazdka:

inet:getstat(Socket)
inet:getstat(Socket, Options) -> {ok, OptionValues} | {error, posix()}

Statystyki to:

  • recv_avg / send_avg - średni rozmiar odebranego / wysłanego pakietu,
  • recv_cnt / send_cnt - ilość odebranych / wysłanych pakietów,
  • recv_dvi / send_dvi - uśrednione odchylenie rozmiaru odebranych / wysłanych pakietów,
  • recv_max / send_max - rozmiar największego odebranego / wysłanego pakietu,
  • recv_oct / send_oct - ilość odebranych / wysłanych bajtów.

Port lokalny

Podaje numer portu lokalnego, z którym jest związane gniazdko:

inet:port(Socket) -> {ok, Port}

Adres lokalny

Podaje lokalny adres i numer portu, z którym jest związane gniazdko:

inet:sockname(Socket) -> {ok, {Address, Port}} | {error, posix()}

Lista otwartych połączeń

(foobar@localhost)1> inet:i().
Port Module   Recv Sent Owner    Local Address   Foreign Address State
64   inet_tcp 0    0    <0.21.0> *:55761         *:*             ACCEPTING
67   inet_tcp 4    21   <0.19.0> localhost:55720 localhost:4369  CONNECTED

Obsługa interfejsów sieciowych

Lista interfejsów

> inet:getiflist().
{ok,["lo","eth0","wlan0"]}

Opcje interfejsów

  • addr – adres interfejsu,
  • broadaddr – adres rozgłoszeniowy,
  • mtu – maximum transfer unit,
  • netmask – maska sieciowa,
  • flags – flagi interfejsu (up, down, broadcast, no_broadcast, pointtopoint, no_pointtopoint, running, multicast),
  • hwaddr – adres sprzętowy (np. MAC).
> inet:ifget("lo",[addr,flags]).
{ok,[{addr,{127,0,0,1}},{flags,[up,loopback,running]}]}

Źródła

Moduły:

Funkcje:

Obsługa gniazdek TCP

Charakterystyka

Nagłówek protokołu TCP

+ 0–3 4–7 8–15 16–31
0 Port nadawcy Port odbiorcy
32 Numer sekwencyjny
64 Numer potwierdzenia
96 Długość nagłówka Zarezerwowane Flagi Szerokość okna
128 Suma kontrolna Wskaźnik priorytetu
160 Opcje (opcjonalnie)
160/192+ Dane

Cechy protokołu TCP:

  • połączeniowy,
  • potwierdzenia,
  • kontrola przepustowości,
  • strumieniowy.

Proces kontrolujący

Proces kontrolujący otrzymuje następujące komunikaty od otwartego (w trybie aktywnym) gniazdka TCP:

{tcp, Socket, Data}
{tcp_closed, Socket}
{tcp_error, Socket, Reason}

Proces kontrolujący gniazdko można zmienić używając poniższej funkcji:

gen_tcp:controlling_process(Socket, Pid) -> ok

Jest to szczególnie przydatne w przypadku konstruowania serwera. Dla każdego nowego połączenia tworzymy osobny proces i przypisujemy mu gniazdko, które będzie obsługiwał.

Obsługa gniazdka

Kto jest po drugiej stronie ?

Gniazdko z protokołem połączeniowym możemy odpytać o to z kim jest połączone:

inet:peername(Socket) -> {ok, {Address, Port}} | {error, posix()}

Nawiązywanie połączenia

gen_tcp:connect(Address, Port, Options) -> {ok, Socket} | {error, Reason}
gen_tcp:connect(Address, Port, Options, Timeout) -> {ok, Socket} | {error, Reason}

Domyślnie Timeout = infinity.

Tworzenie gniazda nasłuchującego

Poniższa funkcja odpowiada wywołaniu funkcji interfejsu BSD: socket, bind, listen. Zatem w jednym kroku mamy utworzone gniazdo nasłuchujące:

gen_tcp:listen(Port, Options) -> {ok, ListenSocket} | {error, Reason}

Opcje dla gniazda są poszerzone o:

  • {backlog, B} – gdzie B >= 0 (domyślnie 5), to domyślna długość kolejki połączeń czekających na zaakceptowanie. Jeśli kolejka jest pełna, kolejne połączenia przychodzące są odrzucane.

Akceptowanie połączeń

gen_tcp:accept(ListenSocket) -> {ok, Socket} | {error, Reason}
gen_tcp:accept(ListenSocket, Timeout) -> {ok, Socket} | {error, Reason}

W wyniku zaakceptowania połączenia tworzy się nowe gniazdo. Jego właścicielem jest proces, który zaakceptował połączenie.

Odbieranie danych

Tylko dla gniazdek otwartych w trybie pasywnym:

gen_tcp:recv(Socket, Length) -> {ok, Packet} | {error, Reason}
gen_tcp:recv(Socket, Length, Timeout) -> {ok, Packet} | {error, Reason}

Parametr długość ma znaczenie gdy odbieramy pakiety w trybie raw (bez interpretacji długości):

  • Length == 0 – przeczytaj wszystkie odebrane bajty,
  • Length > 0 – odczytaj dokładnie Length bajtów.

Uwaga! Co w przypadku jeśli w gniazdku są dane gotowe do odebrania, a strona przeciwna zamknie połączenie? Istnieje możliwość, że nie będzie można tych danych odczytać!

Wysyłanie danych

gen_tcp:send(Socket, Packet) -> ok | {error, Reason}

Zamknięcie gniazda

Częściowe

gen_tcp:shutdown(Socket, How) ->  ok | {error, Reason}

Gdzie How to:

  • read - nie chcemy więcej czytać,
  • write - nie chcemy więcej zapisywać,
  • read_write - działa jak inet:close/1.

Całkowite

gen_tcp:close(Socket) -> ok | {error, Reason}

Przykłady

Prosty klient

Pobieranie strony WWW po protokole HTTP

 1: nano_get_url() ->
 2:     nano_get_url("www.google.com").
 3:
 4: nano_get_url(Host) ->
 5:     {ok,Socket} = gen_tcp:connect(Host,80,[binary, {packet, 0}]), %% (1)
 6:     ok = gen_tcp:send(Socket, "GET / HTTP/1.0\r\n\r\n"),  %% (2)
 7:     receive_data(Socket, []).
 8:
 9: receive_data(Socket, SoFar) ->
10:     receive
11:         {tcp,Socket,Bin} ->
12:             receive_data(Socket, [Bin|SoFar]);
13:         {tcp_closed,Socket} ->
14:             list_to_binary(reverse(SoFar)) %% (5)
15:     end.

Zdalne obliczanie wyrażeń

Klient

 1: nano_client_eval(Str) ->
 2:     {ok, Socket} = gen_tcp:connect("localhost", 2345, [binary, {packet, 4}]),
 3:     ok = gen_tcp:send(Socket, term_to_binary(Str)),
 4:     receive
 5:         {tcp,Socket,Bin} ->
 6:             io:format("Client received binary = ~p~n",[Bin]),
 7:             Val = binary_to_term(Bin),
 8:             io:format("Client result = ~p~n",[Val]),
 9:             gen_tcp:close(Socket)
10:     end.

Serwer

 1: start_nano_server() ->
 2:     {ok, Listen} = gen_tcp:listen(2345, [binary, {packet, 4}, {reuseaddr, true}, {active, true}]),
 3:     {ok, Socket} = gen_tcp:accept(Listen),
 4:     gen_tcp:close(Listen),
 5:     loop(Socket).
 6:
 7: loop(Socket) ->
 8:     receive
 9:         {tcp, Socket, Bin} ->
10:             io:format("Server received binary = ~p~n",[Bin]),
11:             Str = binary_to_term(Bin),
12:             io:format("Server (unpacked)  ~p~n",[Str]),
13:             Reply = lib_misc:string2value(Str),
14:             io:format("Server replying = ~p~n",[Reply]),
15:             gen_tcp:send(Socket, term_to_binary(Reply)),
16:             loop(Socket);
17:         {tcp_closed, Socket} ->
18:             io:format("Server socket closed~n")
19:     end.

Błędny serwer

error_test_server_loop wywali się na atom_to_list

 1: error_test() ->
 2:     spawn(fun() -> error_test_server() end),
 3:     timer:sleep(2000),
 4:     {ok,Socket} = gen_tcp:connect("localhost",4321,[binary, {packet, 2}]),
 5:     io:format("connected to:~p~n",[Socket]),
 6:     gen_tcp:send(Socket, <<"123">>),
 7:     receive
 8:         Any ->
 9:             io:format("Any=~p~n",[Any])
10:     end.
11:
12: error_test_server() ->
13:     {ok, Listen} = gen_tcp:listen(4321, [binary,{packet,2}]),
14:     {ok, Socket} = gen_tcp:accept(Listen),
15:     error_test_server_loop(Socket).
16:
17: error_test_server_loop(Socket) ->
18:     receive
19:         {tcp, Socket, Data} ->
20:             io:format("received:~p~n",[Data]),
21:             atom_to_list(Data),
22:             error_test_server_loop(Socket)
23:     end.

Źródła

Moduły:

Obsługa gniazdek UDP

Proces kontrolujący

Proces kontrolujący dostaje następujące komunikaty od otwartego w trybie aktywnym gniazdka UDP:

{udp, Socket, SrcIP, SrcPort, Data}

gdzie:

  • SrcIP – numer IP nadawcy,
  • SrcPort – numer portu nadawcy,
  • Data – zawartość datagramu.

Uwaga! Jeśli datagram nie mieści się w buforze systemowym to zostanie obcięty bez ostrzeżenia!

Proces kontrolujący gniazdko można zmienić używając poniższej funkcji:

gen_udp:controlling_process(Socket, Pid) -> ok

Charakterystyka protokołu UDP

Nagłówek protokołu UDP

+ Bity 0 - 15 Bity 16 - 31
0 Port nadawcy Port odbiorcy
32 Długość Suma kontrolna
64 Dane

Cechy:

  • bezpołączeniowy,
  • brak kontroli przepływu,
  • brak retransmisji - dopuszcza gubienie lub duplikowanie pakietów,
  • nie zachowuje porządku (porządek odebrania pakietów, może być inny niż wysłania).

Protokół UDP zapewnia nam fragmentację i defragmentację pakietów, oczywiście z pewnymi ograniczeniami:

  • implementacje stosu TCP/IP mają ograniczenia na wielkość datagramów UDP (z reguły 8192 bajtów),
  • defragmentacja pakietu wymaga, żeby stos TCP/IP znał wszystkie fragmenty; jeśli jeden z fragmentów się zgubi lub przyjdzie zbyt późno (czas oczekiwania z reguły wynosi 20-30 sekund) to datagram zostaje porzucony
  • niektóre routery / zapory ogniowe blokują niepoprawnie pakiety ICMP co uniemożliwia odkrycie MTU dla ścieżki i może uniemożliwiać prawidłowe fragmentowanie.

Z reguły unika się wysyłania dużych pakietów UDP. Maksymalny rozmiar nie powodujący problemów to 1500 bajtów dla Ethernetu, a wartość 500 bajtów jest bezpieczna dla większości znanych sieci.

Obsługa gniazdka

Otwieranie gniazda

gen_udp:open(Port) -> {ok, Socket} | {error, Reason}
gen_udp:open(Port, Options) -> {ok, Socket} | {error, Reason}

Odbieranie danych

gen_udp:recv(Socket, Length) -> {ok, Packet} | {error, Reason}
gen_udp:recv(Socket, Length, Timeout) -> {ok, Packet} | {error, Reason}

FIXME A co się stanie jeśli Length < DatagramLength? Będzie można przeczytać resztę danych, czy reszta zostanie porzucona?

Wysyłanie danych

gen_udp:send(Socket, Address, Port, Packet) -> ok | {error, Reason}

Przykłady

Prosty serwer UDP

 1: -module(udp_server).
 2: -export([start_server/0]).
 3:
 4: start_server() ->
 5:     spawn(fun() -> server(4000) end).
 6:
 7: server(Port) ->
 8:     {ok, Socket} = gen_udp:open(Port, [binary]),
 9:     io:format("server opened socket:~p~n",[Socket]),
10:     loop(Socket).
11:
12: loop(Socket) ->
13:     receive
14:     {udp, Socket, Host, Port, Bin} = Msg ->
15:         io:format("server received:~p~n",[Msg]),
16:         Fac = fac(binary_to_term(Bin)),
17:         gen_udp:send(Socket, Host, Port, term_to_binary(Fac)),
18:         loop(Socket)
19:     end.
20:
21: fac(0) -> 1;
22: fac(N) -> N * fac(N-1).

Prosty klient UDP

 1: -module(udp_client).
 2: -export([client/1]).
 3:
 4: client(N) ->
 5:     {ok, Socket} = gen_udp:open(0, [binary]),
 6:     io:format("client opened socket=~p~n",[Socket]),
 7:     ok = gen_udp:send(Socket, "localhost", 4000, term_to_binary(N)),
 8:     Value = receive
 9:         {udp, Socket, _, _, Bin} = Msg ->
10:             io:format("client received:~p~n",[Msg]),
11:             binary_to_term(Bin)
12:         after 2000 ->
13:             0
14:         end,
15:     gen_udp:close(Socket),
16:     Value.

Przykład rozgłaszania po UDP

 1: -module(broadcast).
 2: -compile(export_all).
 3:
 4: send(IoList) ->
 5:     case inet:ifget("eth0", [broadaddr]) of
 6:         {ok, [{broadaddr, Ip}]} ->
 7:             {ok, S} =  gen_udp:open(5000, [{broadcast, true}]),
 8:             gen_udp:send(S, Ip, 6000, IoList),
 9:             gen_udp:close(S);
10:         _ ->
11:             io:format("Bad interface name, or~n"
12:                       "broadcasting not supported~n")
13:     end.
14:
15: listen() ->
16:     {ok, _} = gen_udp:open(6000),
17:     loop().
18:
19: loop() ->
20:     receive
21:         Any ->
22:             io:format("received:~p~n", [Any]),
23:             loop()
24:     end.

Źródła

Moduły:

Gniazdka TLS/SSL

Podstawy

Algorytmy szyfrujące

Klucze symetryczne

Klucz, którego używa się zarówno do kodowania jak i dekodowania. W większości przypadków znacznie krótszy (128 bitów, 256 bitów) niż klucze asymetryczne (1024, 2048, 4096 bitów). Z tego względu tańszy obliczeniowo. Funkcje szyfrującą pojedynczy blok wiąże się w łańcuchy przy pomocy algorytmów wiązania bloków zaszyfrowanych.

Metody szyfrowania przy użyciu kluczy symetrycznych używane w TLS:

  • DES + CBC
  • AES + CBC
  • IDEA + CBC
  • 3DES EDE + CBC
  • RC4 (strumieniowy)
CBC

Metoda przekształcania metod szyfrowania z wersji blokowej na strumieniową.

Klucze asymetryczne

Kosztowne w obliczeniach. Przeważnie długie (powyżej 1024 bitów).

W TLS wykorzystuje się kilka algorytmów:

  • RSA,
  • Diffie-Hellman.
RSA

Obliczanie kluczy:

  • Wybieramy losowo dwie duże liczby pierwsze $$p$$ i $$q$$
  • Obliczamy wartość $$n = pq$$
  • Obliczamy wartość funkcji Eulera dla n: $$\varphi(n) = (p - 1)(q - 1)$$
  • Wybieramy liczbę $$e$$ $$(1 < e < \varphi(n))$$ względnie pierwszą z $$\varphi(n)$$
  • Znajdujemy liczbę $$d$$ odwrotną do $$e\mbox{ mod }\varphi(n)$$: $$e = d^{-1}\mbox{ mod }\varphi(n)$$

Kluczem publiczny jest para $$(n, e)$$, a prywatnym para $$(n, d)$$.

Wiadomość dzieli się przed zaszyfrowaniem na bloki $$m_i$$ długości nie większej niż n.

Wiemy, że $$(m^e)^{d} = (m^d)^{e} = m$$.

Szyfrogram wyliczamy następująco: $$c_{i} = (m_{i})^e \mbox{ mod } n$$. Deszyfrowanie przebiega według wzoru: $$m_{i} = (c_{i})^d \mbox{ mod } n$$.

Kryptograficzne funkcje skrótu

Właściwości kryptograficznej funkcji skrótu:

  • łatwo obliczalna wartość skrótu dla dowolnego komunikatu
  • trudno odnaleźć komunikat z daną wartością skrótu,
  • trudno zmodyfikować wiadomość bez zmiany wartości skrótu,
  • trudno znaleźć dwie wiadomości z taką samą wartością skrótu.

Funkcje skrótu używane w TLS:

  • SHA,
  • MD5.

Podpis cyfrowy

Kod uwierzytelniania wiadomości

Może nam zależeć jedynie na tym by nikt nie był w stanie zmienić naszej wiadomości w trakcie jej przesyłania. Natomiast niejawność danych, czy źródło pochodzenia nie jest istotne. Zakładamy, że wszyscy posiadający klucz są w stanie tworzyć i modyfikować wiadomości. W tym celu możemy użyć algorytmów z rodziny Message Authentication Code lub Message Integrity Code.

Certyfikaty SSL

Każdy certyfikat SSL posiada co najmniej następujące informacje:

  • wydawca – opis wydawcy certyfikatu,
  • podmiot – opis tożsamości, dla której certyfikat został stworzony przez wydawcę,
  • cyfrowy podpis wydawcy (wraz z informacją o algorytmie) – używany w celu zweryfikowania certyfikatu,
  • klucz publiczny podmiotu (wraz z informacją o użytym algorytmie),
  • data ważności certyfikatu,
  • numer seryjny.

Urzędy certyfikacji to podmioty wydające certyfikaty i poświadczające ich autentyczność.

  • główne (ang. root CA),
  • pośrednie (ang. intermediate CA).

Certyfikaty główne są certyfikatami z podpisem własnym ang. self-signed – w których podmiot i wydawca są tacy sami. Główne urzędy certyfikacji to przeważnie znane organizacje lub firmy (np. VeriSign).

Każda implementacja protokołu TLS celem uwierzytelniania certyfikatów powinna posiadać zestaw zaufanych certyfikatów.

Algorytm uwierzytelniania certyfikatów

Algorytm uwierzytelniania certyfikatów polega na weryfikowaniu podpisu wydawcy. Certyfikaty są połączone w łańcuch zależności. Certyfikat uznajemy za zweryfikowany jeśli jeden z jego rodziców znajduje się na liście zaufanych certyfikatów.

Niech $$C_1, C_2, C_3, …, C_n$$ jest ciągiem certyfikatów, pierwszy z nich jest certyfikatem głównym, a ostatni certyfikatem użytkownika. Niech $$C_i$$ jest wydawcą $$C_{i+1}$$.

Krok algorytmu:

  1. bierzemy publiczny klucz wydawcy z $$C_{i-1}$$,
  2. aplikujemy go do podpisu cyfrowego zawartego w $$C_i$$ i otrzymujemy skrót $$H_1$$ wiadomości certyfikatu $$C_i$$,
  3. obliczamy sami skrót $$H_2$$ wiadomości $$C_i$$,
  4. porównujemy $$H_1$$ i $$H_2$$,
  5. jeśli się zgadzają to certyfikat został sprawdzony.

Tworzenie certyfikatów SSL

self-signed certificates

Podmiot certyfikatu jest jednocześnie urzędem certyfikującym. Najszybsza metoda wygenerowania certyfikatów do testów.

Tworzenie klucza RSA
$ openssl genrsa 1024 > host.key
$ chmod 400 host.key
Tworzenie certyfikatu
$ openssl req -new -x509 -nodes -sha1 -days 365 -key host.key -out host.cert
Country Name (2 letter code) [AU]:PL
State or Province Name (full name) [Some-State]:dolnoslaskie
Locality Name (eg, city) []:Wroclaw
Organization Name (eg, company) [Internet Widgits Pty Ltd]:Uniwersytet Wroclawski
Organizational Unit Name (eg, section) []:Instytut Informatyki
Common Name (eg, YOUR name) []:cahirwpz.cs.uni.wroc.pl
Email Address []:cahirwpz at ii [...]
Wyświetlanie nowo utworzonego certyfikatu
$ openssl x509 -noout -text -in host.cert 
Certificate:
    Data:
        Version: 3 (0x2)
        Serial Number:
            f0:bb:46:21:bd:34:b2:df
        Signature Algorithm: sha1WithRSAEncryption
        Issuer: C=PL, ST=dolnoslaskie, L=Wroclaw, O=Uniwersytet Wroclawski, OU=Instytut Informatyki, CN=cahirwpz.cs.uni.wroc.pl/emailAddress=cahirwpz at ii [...]
        Validity
            Not Before: May 18 10:30:40 2010 GMT
            Not After : May 18 10:30:40 2011 GMT
        Subject: C=PL, ST=dolnoslaskie, L=Wroclaw, O=Uniwersytet Wroclawski, OU=Instytut Informatyki, CN=cahirwpz.cs.uni.wroc.pl/emailAddress=cahirwpz at ii [...]
        Subject Public Key Info:
            Public Key Algorithm: rsaEncryption
            RSA Public Key: (1024 bit)
                Modulus (1024 bit):
                    00:b9:f6:a1:80:9e:65:ab:13:31:f2:26:03:9a:3d:
                    88:3b:a4:a8:c8:49:08:2b:00:53:6e:f9:56:be:9b:
                    66:a0:85:6e:02:2b:62:30:9e:ac:a4:41:9f:34:23:
                    4e:aa:82:28:f7:4c:bf:ea:a1:9e:6f:d1:43:d3:2d:
                    3a:08:9b:d2:68:6e:88:10:5c:14:04:bc:17:49:7c:
                    6d:82:41:85:f8:d4:be:ef:b0:6a:07:c6:77:46:aa:
                    b8:a0:3e:44:44:c9:43:e1:4b:4f:a0:62:82:e0:d5:
                    c9:1a:b6:3f:34:5a:50:b0:fd:2e:0c:87:a0:6c:f1:
                    44:a2:c4:53:fb:78:36:57:89
                Exponent: 65537 (0x10001)
        X509v3 extensions:
            X509v3 Subject Key Identifier:
                90:19:5E:BD:74:24:46:A6:42:16:E5:F0:EB:C7:32:85:9D:2A:87:57
            X509v3 Authority Key Identifier:
                keyid:90:19:5E:BD:74:24:46:A6:42:16:E5:F0:EB:C7:32:85:9D:2A:87:57
                DirName:/C=PL/ST=dolnoslaskie/L=Wroclaw/O=Uniwersytet Wroclawski/OU=Instytut Informatyki/CN=cahirwpz.cs.uni.wroc.pl/emailAddress=cahirwpz at ii [...]
                serial:F0:BB:46:21:BD:34:B2:DF

            X509v3 Basic Constraints:
                CA:TRUE
    Signature Algorithm: sha1WithRSAEncryption
        70:21:c4:0e:fb:f5:10:c4:bb:a0:95:16:a7:7f:db:94:26:df:
        4c:e9:f2:00:0d:10:00:2b:4d:33:56:d8:c7:9a:f9:63:84:7a:
        42:63:73:fc:a2:4d:9e:8e:69:7b:d2:e5:5d:f1:7f:92:48:a0:
        60:c3:07:3b:9c:b6:97:ad:1a:99:0d:84:e8:66:45:29:29:f6:
        8b:a3:54:1d:57:cf:ea:1d:f1:cc:a5:d9:5d:b3:c7:9e:3c:8d:
        7f:2a:85:c4:21:5a:11:de:23:1e:15:0d:ac:31:4a:d4:8f:43:
        66:6f:cb:ee:0a:06:e4:4e:1c:8d:9c:2f:63:5b:93:81:44:17:
        c6:b5

Protokół SSL/TLS

Zestaw algorytmów szyfrujących

Na zestaw algorytmów szyfrujących (ang. cipher suite) składają się:

  • algorytmy wymiany kluczy,
  • algorytmy uwierzytelniania użytkowników,
  • algorytmy szyfrowania dużej ilości danych,
  • kod uwierzytelniania wiadomości,
  • funkcje pseudo-losowe.

Negocjacja połączenia

Trzy rodzaje:

  • zwykła
  • z autoryzacją
  • skrócona

Pełna negocjacja sesji

Skrócona negocjacja sesji

Obsługa gniazdek SSL/TLS

Aplikacja SSL

Przed użyciem funkcji związanych z TLS/SSL trzeba wystartować aplikację wspomagającą obsługę protokołu:

ssl:start()
ssl:stop()

Poniższa funkcja pokazuje wersję aplikacji SSL i wspierane protokoły:

> ssl:versions().
[{ssl_app,"3.10.8"},
 {supported,[tlsv1,sslv3]},
 {available,[tlsv1,sslv3]}]

W chwili bieżącej dwie implementacje protokołu TLS/SSL. Możemy sprawdzić listę algorytmów szyfrujących, które są wspierane:

> ssl:cipher_suites().
> ssl:cipher_suites(erlang).
[{rsa,'3des_ede_cbc',sha,no_export},
 {rsa,aes_128_cbc,sha,ignore},
 {rsa,rc4_128,sha,no_export},
 {rsa,rc4_128,md5,no_export},
 {rsa,des_cbc,sha,no_export}]
> ssl:cipher_suites(openssl).
["DES-CBC3-SHA","AES128-SHA","RC4-SHA","RC4-MD5",
 "DES-CBC-SHA"]

Proces kontrolujący i komunikaty

Podobnie jak dla reszty typów gniazdek, jeśli takowe znajduje się w stanie aktywnym, do procesu kontrolującego przychodzą komunikaty postaci:

  • {ssl, Socket, Data}
  • {ssl_closed, Socket}
  • {ssl_error, Socket, Reason}

Oczywiście prawo własności gniazdka można przetransferować poniższą funkcją:

ssl:controlling_process(SslSocket, NewOwner) ->  ok | {error, Reason}

Informacje związane z połączeniem

ssl:connection_info(SslSocket) ->  {ok, {ProtocolVersion, CipherSuite}} | {error, Reason}

Zwraca używany zestaw algorytmów szyfrujących i wersję protokołu.

ssl:peercert(Socket) -> {ok, Cert} | {error, Reason}

Powyższa funkcja zwraca certyfikat z należący do drugiej strony połączenia. Certyfikat zostanie zwrócony w postaci zakodowanej ASN.1 DER, którą można rozkodować używając public_key:pkix_decode_cert/2.

Opcje tworzenia gniazdek

  • {verify, verify_none | verify_peer} – włącza wysyłanie pakietu CertificateRequest po stronie serwera; po stronie klienta dokonuje weryfikacji certyfikatu wysłanego przez serwer,
  • {fail_if_no_peer_cert, true | false} – ważne po stronie serwera, powoduje niepowodzenie negocjacji jeśli klient nie posiada certyfikatu (tj. certyfikat jest pusty),
  • {depth, Integer} – maksymalna długość łańcucha certyfikatów, (domyślnie 1),
  • {certfile, Path} – ścieżka do certyfikatu (obowiązkowe dla serwera, dla klienta opcjonalne),
  • {keyfile, Path} – ścieżka do klucza prywatnego,
  • {password, Password} – używane jeśli klucz prywatny jest podpisany hasłem,
  • {cacertfile, Path} – ścieżka do pliku przechowującego zaufane certyfikaty,
  • {ciphers, Ciphers} – zestaw preferowanych algorytmów szyfrujących,
  • {ssl_imp, new | old} – używana implementacja (old – OpenSSL, new – wbudowana erlangowa),
  • {reuse_sessions, true | false} – używanie skróconej negocjacji sesji,
  • {reuse_session, fun(SuggestedSessionId, PeerCert, Compression, CipherSuite) → boolean()} – funkcja odpowiadająca na pytanie czy dany proces negocjacji sesji można skrócić, czy trzeba negocjować od początku.

Nasłuchiwanie

Poniższe funkcje akceptują połączenie na poziomie protokołu TCP, aby móc używać nowego gniazdka należy na nim dokonać negocjacji sesji.

ssl:transport_accept(Socket) -> Result
ssl:transport_accept(Socket, Timeout) -> Result
 
Result = {ok, NewSocket} | {error, Reason}

Negocjacja SSL/TLS

Negocjacja i rozpoczęcie sesji szyfrowanej może się rozpocząć w dwóch momentach:

  1. od razu po nawiązaniu połączenia,
  2. w pewnym z góry określonym momencie wyznaczonym przez protokół nieszyfrowany (np. STARTTLS w FTP lub IMAP).

Negocjacja sesji po stronie klienta:

ssl:connect(Socket, SslOptions)
ssl:connect(Socket, SslOptions, Timeout)

Negocjacja po stronie serwera:

ssl:ssl_accept(ListenSocket)
ssl:ssl_accept(ListenSocket, Timeout)
ssl:ssl_accept(ListenSocket, SslOptions)
ssl:ssl_accept(ListenSocket, SslOptions, Timeout)

Źródła

 
erlang/sockets.txt · Last modified: 2010/05/19 15:28 by Krystian Bacławski
 
Except where otherwise noted, content on this wiki is licensed under the following license:CC Attribution-Noncommercial-No Derivative Works 3.0 Unported
Recent changes RSS feed Donate Powered by PHP Valid XHTML 1.0 Valid CSS Driven by DokuWiki