Serwisy partnerskie:
Close icon
Serwisy partnerskie

Filozofia sieci. Protokół ARP

Article Image
Od numeru z grudnia 2018 roku na łamach „Elektroniki dla Wszystkich” publikowany jest cykl artykułów dotyczący systemu automatyki domowej Infinity. Opisywana konstrukcja jest dosyć rozbudowana, a jednym z istotnych jej elementów jest serwer stron www. Omówienie samego serwera www było przedstawione w kilku kolejnych numerach i zdecydowanie nie wyczerpało tematu (oczywiście dotyczącego sieci). Z tego powodu powstała koncepcja zaprezentowania dodatkowego materiału poświęconego wyłącznie rozważaniom na temat  obsługi sieci. Do ilustracji działania jest wykorzystana dokładnie ta sama wersja oprogramowania sieciowego, jaka jest zastosowana w systemie Infinity. Rozważania również będą oparte o ten sam mikrokontroler LPC2378 produkowany przez firmę NXP (dawniej Philips).

Zaprezentowane oprogramowanie związane z obsługą sieci ewoluowało przez wiele lat i jest sumą doświadczeń i potrzeb zrealizowanych wielu konstrukcji. Cechą charakterystyczną oprogramowania jest to, że nie wymaga środowiska jakiegokolwiek systemu operacyjnego (RTOS), co oznacza, że jest w pełni „samodzielne”. Oczywiście implikuje to kilka problemów, które normalnie są rozwiązywane przez systemy operacyjne. Opisywane i proponowane „eksperymenty sieciowe” mogą być realizowane w oparciu o to samo hardware, jakie jest opisane w systemie Infinity.

Do tworzenia oprogramowania wykorzystane jest oprogramowanie narzędziowe, które bezpłatnie do celów niekomercyjnych można pobrać ze strony www.keil.com (wyklikać pobranie narzędzia MDK dla mikrokontrolerów ARM). Instalacja nie należy do skomplikowanych. Posługiwanie się tym narzędziem jest wystarczająco intuicyjne; w najprostszym wypadku wystarczy otworzyć odpowiedni udostępniony projekt (w materiałach dodatkowych jest dokument opisujący posługiwanie się tym oprogramowaniem narzędziowym, który można pobrać ze strony Elportalu).

Na początek zaczniemy od sprawy pozornie mało istotnej – protokołu ARP, który nie bierze udziału w przesyłaniu danych (nie niesie w sobie żadnych danych użytkowych) i ma znaczenie „organizacyjne”. Jednak jest to bardzo istotna funkcjonalność, ponieważ bez niej w sieci nic nie zadziała.

Przedstawienie tego protokołu zilustruję przykładami, lecz zanim przejdę do programów demonstracyjnych, opiszę podstawowe informacje związane z budową i funkcjonalnością ramki ethernetowej – podstawowej jednostki transmisyjnej. W sieci komputerowej krążą ramki ethernetowe, które w sobie niosą różne informacje. Jako ramkę należy rozumieć blok oktetów (bajtów) o określonej wielkości (nie większej niż 0x600) przesyłanych jako pojedyncza struktura. Ogólną budowę takiej ramki pokazuje rysunek 1.

Rysunek 1.

Początkowy jej obszar zawiera zawsze następujące dane: adres MAC odbiorcy (zajmujący 6 oktetów), adres MAC nadawcy (zajmujący 6 oktetów), typ (czyli znaczenie obszaru „Dane”, zajmujący 2 oktety) oraz różnej wielkości i o różnym znaczeniu obszar „Dane”. Jednym z podstawowych protokołów używanych w sieciach jest wspomniany protokół ARP (od ang. Address Resolution Protocol) pozwalający przekształcić logiczny adres IP na sprzętowy adres MAC. Chcąc przesłać cokolwiek z jakiegoś komputera do naszego urządzenia, nadawca musi znać nasz adres MAC, gdyż właśnie te adresy określają adresata w sieci (z tego między innymi wynika wymóg unikalności w sieci adresów MAC; każde urządzenie przyłączone do sieci musi mieć swój unikalny adres). Z drugiej strony, w świecie użytkowników, powszechnie posługujemy się adresacją IP (zapisywaną w znanej notacji czterech liczb rozdzielonych kropką). Chcąc przesłać ze stacji A (o określonym adresie IP) do stacji B (o innym adresie IP) jakieś informacje, posługujemy się adresem IP (program w urządzeniu lub w komputerze, wysyłając cokolwiek, podaje jako adresata właśnie adres IP). Z wcześniejszego zdania wynika, że ramki ethernetowe są adresowane jedynie w oparciu o adres MAC. W związku z tym istnieje potrzeba „przekształcenia” adresu IP na adres MAC. Właśnie do tego służy protokół ARP.

Rysunek 2.

Gdy pole Typ (rysunek 1) zawiera 0x806, wtedy pole Dane interpretowane jest jako pakiet ARP, pokazuje to rysunek 2. Budowa informacji określanej jako „Ramka ARP” jest widoczna na rysunku 3. Struktury danych występujących w sieciach są zwyczajowo pokazywane w układzie 32-bitowym (jeden rządek dotyczy 4 kolejnych bajtów), kolejne wiersze są kolejnymi słowami 32-bitowymi. Wiele pól występujących w tych strukturach jest zapisywana na określonych pozycjach bitowych (zajmują mniej niż 32 bity, co odzwierciedla rysunek). 

Rysunek 3.

Pakiet ARP (zarówno zapytania, jak i odpowiedzi) zawiera następujące informacje:

  • HTYPE – typ protokołu warstwy fizycznej, dla sieci Ethernet to pole ma wartość 1,
  • PTYPE – typ protokołu wyższej warstwy, dla sieci Ethernet w wersji IPV4 to pole ma wartość 800 hex,
  • HLEN – długość adresu sprzętowego (MAC) podana w bajtach, ma wartość 6 (adres MAC zajmuje 6 oktetów),
  • PLEN – długość adresu protokołu wyższej warstwy, dla sieci Ethernet IPV4 to pole ma wartość 4 (adres IP zajmuje 4 oktety),
  • OPER – kod realizowanej operacji, nas interesują dwa warianty: 1 – zapytanie ARP i 2 – odpowiedź ARP (jest ich jeszcze dwa, ale jak do tej pory nie miałem potrzeby ich użycia, więc pomijam w implementacji),
  • SHA – adres sprzętowy źródła (strony pytającej) – sprzętowy adres MAC nadawcy, 6 kolejnych oktetów,
  • SPA – adres protokołu wyższej warstwy źródła (strony pytającej) – adres IP nadawcy, 4 kolejne oktety,
  • THA – adres sprzętowy przeznaczenia (strony odpowiadającej) – sprzętowy adres MAC odbiorcy, 6 kolejnych oktetów,
  • TPA – adres protokołu wyższej warstwy przeznaczenia (strony odpowiadającej) – adres IP odbiorcy, 4 kolejne oktety.

Ponieważ problem najlepiej jest zilustrować jakimś przykładem, rozpatrzmy instalację sieciową pokazaną na rysunku 4. Składa się ona z komputera o określonym adresie IP (192.168.0.68) i adresie MAC (00-18-7D-0A-6E-25) oraz z modułu do eksperymentów zbudowanego na bazie mikrokontrolera LPC2378 obsługującego sieć Ethernet o innym adresie IP (192.168.0.55) i innym adresie MAC (08-14-16-22-28-2D). Podana adresacja IP dotyczy mojej domowej sieci komputerowej, która nie musi odpowiadać każdemu Czytelnikowi, toteż w przypadkach „braku kompatybilności” każdy może wnieść korekty. Adresacja MAC jest cechą niejako fabryczną, nadaną przez producenta sprzętu komputerowego, a w przypadku modułu ethernetowego jest „wylosowana” przeze mnie. Podaję jednocześnie adresację IP i MAC, by można było precyzyjnie określić, co i dokąd jest przesyłane.

Rysunek 4.

Potrzebę użycia protokołu ARP pokaże następujący przykład. Komputer (192.168.0.68) wysyła do modułu (192.168.0.55) sygnał PING. Jest to sygnał używany do diagnostyki drożności sieci: jeżeli stacja A wysyła sygnał PING do stacji B, która generuje zwrotną odpowiedź docierająca do nadawcy (stacja A), to należy uznać, że istnieje drożność połączenia. O szczegółach PING na razie nie mówimy, chcę jedynie uświadomić Czytelnikom istnienie takiego mechanizmu. Komputer (192.168.0.68) wygeneruje odpowiedni pakiet zawierający sygnał PING. By utworzone dane dotarły do adresata (do modułu o IP 192.168.0.55), komputer musi znać jego adres MAC. By uzyskać takie dane, wcześniej (przed wysłaniem PING) musi wysłać zapytanie ARP. Ponieważ wysyłając ramkę ARP, komputer nie zna adresu MAC modułu z mikrokontrolerem LPC2378, pole adresata (rysunek 5) zawiera wszystkie bity ustawione (tak jakby adres MAC miał wartość FF-FF-FF-FF-FF-FF), natomiast pole przewidziane na adres MAC (THA) wewnątrz pakietu ARP (rysunek 3) jest wyzerowane.

Rysunek 5.

Wartość MAC=FF-FF-FF-FF-FF-FF to adres rozgłoszeniowy (do wszystkich), czyli każda karta sieciowa przyłączona do lokalnej sieci ethernetowej odbierze taką ramkę. Ale jedynie „posiadacz” adresu IP wymienionego w zapytaniu odpowiada na zapytanie. Strona odpowiadająca „zna” wszystkie szczegóły, by móc wygenerować odpowiedź, adres IP oraz adres MAC strony pytającej jest zawarty w zapytaniu ARP, własny adres IP oraz MAC jest znany (w końcu każdy wie, jak się nazywa). To pozwala na wysłanie odpowiedzi na zapytanie ARP do strony pytającej (struktura pakietu jest identyczna, różni się jedynie kodem w polu OPER).

Do zilustrowania działania został napisany odpowiedni program demonstracyjny dla mikrokontrolera LPC2378. Jest to bardzo prosty program, którego zadaniem jest jedynie odpowiadać na zapytania ARP. Program jest oparty o te same pliki obsługi sieci, które zostały opisane przy okazji serwera www systemu Infinity. Funkcja główna programu (main) jest pokazana na listingu 1 (Uwaga! Wszystkie listingi wymienione w artykule dostępne są w Elportalu).

Listing 1.

int main ( void )
{
  /*-------------------------------------------------------------------------*/
  SysInit ( ) ;
  HardwareInit ( )  ;
  SoftwareInit ( ) ;
  UART0HardwInit( FOSC , N81Mode , 9600 ) ;
  HelloMessage ( ) ;
  StartNet ( & ARPDemoInstance ) ;
  for ( ; ; )
  {
    ARPDemoInstance . EMACLayer -> CheckEthInterface ( ) ;
  } /* loop */ ;
} /* main */

Po zainicjowaniu sieci (wywołanie funkcji StartNet), która jest pokazana na listingu 2, zespół EMAC wraz z układem PHY jest gotowy do obsługi sieci na podstawowym poziomie (fizycznym). Sposób programowego zainicjowania „katy sieciowej” modułu ethernetowego został wystarczająco dokładnie opisany w cyklu artykułów dotyczących systemu Infinity, więc zainteresowani Czytelnicy powinni sięgnąć do odpowiednich numerów „Elektroniki dla Wszystkich”. Inicjacja obsługi sieci sprowadza się to do utworzenia instancji odpowiedzialnej za przetwarzanie ramek ethernetowych jedynie w zakresie protokołu ARP. Wywołana funkcja InitPHYLayerInstance nadaje interfejsowi sieciowemu tylko adres MAC oraz tworzy dowiązania w instancji do podstawowych operacji nadawania oraz odbierania ramek ethernetowych.

Listing 2.

static void StartNet ( PtrARPDemoInstanceRecT Instance )
{
  EtherSpeedMode EthMode ;
  UCHAR LocalMyMACAddr [ 6 ] ;
  USHORT Loop ;
  /*-------------------------------------------------------------------------*/
  for ( Loop = 0 ; Loop < 6 ; Loop ++ )
    LocalMyMACAddr [ Loop ] = MyMACAddr [ Loop ] ;
  EthMode = Mode_AutoNeg ;
//  EthMode = Mode_10MBIT ;
//  EthMode = Mode_100MBIT ;
  Instance -> EMACLayer = InitPHYLayerInstance ( LocalMyMACAddr , EthMode ) ;
  InitIPLayerMemoryService ( ) ;
  ARPDemoInstance . EMACLayer -> InterfaceProcessFrame = FrameProcessing ;
  ARPDemoInstance . EMACLayer -> InterfaceAllocFrameMemory = FrameMemoryAllocate ;
  ARPDemoInstance . EMACLayer -> InterfaceDeallocFrameMemory = FrameMemoryDeallocate ;
} /* StartNet */

Ta bardzo uboga funkcjonalność musi zostać uzupełniona dodatkowymi powiązaniami dotyczącymi przetwarzania odebranych ramek, która powstaje w wyniku wskazania wybranych pół instancji na rzeczywiste funkcje odpowiedzialne za realizację określonych czynności (FrameProcessing – realizacja funkcjonalności związanej z odebraną ramką, FrameMemoryAllocate – funkcja do przydziału pamięci na ramkę oraz FrameMemoryDeallocate – funkcja zwalniająca pamięć zajmowaną przez ramkę ethernetową). Sama obsługa przydziału i zwalniania pamięci również  wymaga zainicjowania (wywołanie InitIPLayerMemoryService). W typowym rozwiązaniu, gdzie występuje obsługa protokołu IP, te dodatkowe funkcje są dowiązywane w momencie tworzenia instancji związanej z przetwarzaniem pakietów IP, jednak w tym programie demonstracyjnym pakiety IP nie są rozpatrywane (demo dotyczy jedynie protokołu ARP). Po tych operacjach konfigurujących obsługę sieci program może jedynie odebrać zapytanie ARP o adres MAC oraz wygenerować właściwą odpowiedź. Trudno to nazwać obsługą sieci, jednak ta funkcjonalność jest niezbędna w każdym programie obsługującym sieć Ethernet.

Listing 3.

static void PoolEthInterface ( void )
{
  ULONG RxDataLength ;
  ULONG Index ;
  ULONG FrameStatus ;
  ULONG * SourcePointer ;
  ULONG * DestinationPointer ;
  PtrOSFrameRecT Frame ;
  RxDescriptor_RecT * WorkRxDescr ;
  RxStatus_RecT * WorkRxStatus ;
  /*-------------------------------------------------------------------------*/
  if ( MAC_RXPRODUCEINDEX != MAC_RXCONSUMEINDEX )
  {
    Index = MAC_RXCONSUMEINDEX ;
    WorkRxDescr = ( RxDescriptor_RecT * ) RxDescrBaseAdr ;
    WorkRxDescr += Index ;
    WorkRxStatus = ( RxStatus_RecT * ) RxStatusBaseAdr ;
    WorkRxStatus += Index ;
    FrameStatus = WorkRxStatus -> StatusInfo ;
    RxDataLength = ( FrameStatus & RINFO_SIZE ) - 3 ;
    if ( RxDataLength > EtherMTU )
    {
      Frame = NIL ;
    } /* if ... */
    else
    {
      Frame = EMACInstanceRec . InterfaceAllocFrameMemory ( RxDataLength ) ;
      if ( Frame )
      {
        DestinationPointer = ( ULONG * ) ( & Frame -> Data [ 0 ] ) ;
        Frame -> Length = RxDataLength ;
        Frame -> Index = 0 ;
        SourcePointer = ( ULONG * ) WorkRxDescr -> Packet ;
        RxDataLength = ( RxDataLength + 3 ) >> 2 ;
        for ( ; RxDataLength ; RxDataLength -- )
          * DestinationPointer ++ = * SourcePointer ++ ;
      } /*if */ ;
    } /* if ... else */ ;
    Index = MAC_RXCONSUMEINDEX ;
    if ( ++ Index == RxFragmentsPcs )
      Index = 0 ;
    MAC_RXCONSUMEINDEX = Index ;
    if ( Frame )
      EMACInstanceRec . InterfaceProcessFrame ( Frame ) ;
  } /* if */ ;
} /* PoolEthInterface */

Ta uproszczona funkcja (jedno z pól instancji, będące odniesieniem do funkcji: ARPDemoInstance.EMACLayer->CheckEthInterface, listing 1, wskazuje na funkcję PoolEthInterface, listing 3) jest jedynym elementem realizowanej w pętli głównej funkcji main. Jej zadaniem jest sprawdzenie, czy zespół EMAC odebrał ramkę ethernetową i ewentualne przepisanie z obszaru kolejek zespołu EMAC treści ramki ethernetowej do obszaru przydzielonego przez odpowiednią funkcję oraz „rozpoznania” jej znaczenia jako wywołanie EMACInstanceRec.InterfaceProcessFrame, które prowadzi do funkcji FrameProcessing. Jej postać pokazuje listing 4.

Listing 4.

static void FrameProcessing ( PtrOSFrameRecT Frame )
{
  EtherPackedHeaderRecT EtherPackedHeader ;  
  /*-------------------------------------------------------------------------*/
  GetEthHeader ( & EtherPackedHeader , Frame ) ;
  SetIPHeaderOrgin ( Frame ) ;
  if ( EtherPackedHeader . LengthOrType >= 1518 )
  {
    if ( EtherPackedHeader . LengthOrType == FRAME_ARP )
    {
      ProcessARPPacked ( Frame ) ;
    } /* if */ ;
  } /* if */ ;
  ARPDemoInstance . EMACLayer -> InterfaceDeallocFrameMemory ( Frame ) ;
} /* FrameProcessing */

Wniesiony w parametrach wywołania funkcji wskaźnik do obszaru ramki zawiera kompletną odebraną z sieci ramkę ethernetową. Z tego obszaru „wyjęty” zostaje nagłówek ethernetowy (pokazany na rysunku 1) w wyniku wywołania funkcji GetEthHeader. W zmiennej lokalnej EtherPackedHeader znajduje się adres MAC odbiorcy (czyli nasz, ewentualnie może zawierać same jedynki jako adres rozgłoszeniowy), adres MAC nadawcy oraz informacja o znaczeniu odebranej ramki. W przypadku gdy pole „Typ” zawiera 0x806, stała o nazwie FRAME_ARP (rysunek 2) to oznacza, że została odebrana ramka protokołu ARP. Jej obsługa znajduje się w funkcji ProcessARPPacked (listing 5).

Listing 5.

static void ProcessARPPacked ( PtrOSFrameRecT Frame )
{
  ARPHeaderRecT ARPPacked ;
  EtherPackedHeaderRecT EtherPackedHeader ;  
  /*-------------------------------------------------------------------------*/
  ARPPacked . HardwareType = GetUSHORTSwapField ( Frame , EtherPackedHeaderRecTSize + HardwareTypeOffset ) ;
  ARPPacked . ProtocolType = GetUSHORTSwapField ( Frame , EtherPackedHeaderRecTSize + ProtocolTypeOffset ) ;
  ARPPacked . AddressLength = GetUSHORTSwapField ( Frame , EtherPackedHeaderRecTSize + AddressLengthOffset ) ;
  ARPPacked . Operation = GetUSHORTSwapField ( Frame , EtherPackedHeaderRecTSize + OperationOffset ) ;
  GetByteArrayField ( ARPPacked . SenderMACAddress , Frame , EtherPackedHeaderRecTSize + SenderMACAddressOffset ,
                      EthMACAddressSize ) ;
  ARPPacked . SenderIPAddress = GetULONGSwapField ( Frame , EtherPackedHeaderRecTSize + SenderIPAddressOffset ) ;
  GetByteArrayField ( ARPPacked . TargetMACAddress , Frame , EtherPackedHeaderRecTSize + TargetMACAddressOffset ,
                      EthMACAddressSize ) ;
  ARPPacked . TargetIPAddress = GetULONGSwapField ( Frame , EtherPackedHeaderRecTSize + TargerIPAddressOffset ) ;
  if ( ARPPacked . HardwareType == EthHardwType ) 
  {
    if ( ARPPacked . ProtocolType == FRAME_IP )
    {
      if ( ARPPacked . AddressLength == IP_HLen_PLen )
      {
        switch ( ARPPacked . Operation )
        {
          case ARP_request              :
            GetEthHeader ( & EtherPackedHeader , Frame ) ;
            UART0SendString ( FrameMessage1 ) ;
            DisplayMAC ( EtherPackedHeader . DestinMACAddress ) ;
            UART0SendString ( FrameMessage2 ) ;
            if ( ARPPacked . TargetIPAddress == MachineIPAddress )
            {
              UART0SendString ( FrameMessage3 ) ;
              SendARPResponse ( ARPDemoInstance . EMACLayer , & ARPPacked ) ;
            } /* if */ ;
            break ;
          case ARP_response             :
            RecvARPResponse ( ARPDemoInstance . EMACLayer , & ARPPacked ) ;
            break ;
          case RARP_request             :
            break ;
          case RARP_response            :
            break ;
          default                       :
            break ;
        } /* switch */ ;
      } /* if */ ;
    } /* if */ ;
  } /* if */ ;
} /* ProcessARPPacked */

Sprowadza się ona do rozpakowania pakietu ARP w wyniku wywołania kilku funkcji „wyjmujących” z obszaru ramki z określonego położenia danych binarnych. Ze względu na „wielkość endian” (little endian, big endian) nie można „nałożyć” na obszar ramki odpowiedniej struktury, dane muszą być „wyjmowane” z zamianą kolejności bajtów w strukturach wielobajtowych. Po kompletnym rozpakowaniu pozostało sprawdzić podstawowe elementy struktury zapytania ARP (stałe elementy muszą być zgodne z rysunkiem 3: HTYPE, PTYPE, HLEN, PLEN). Jeżeli pole OPER (rysunek 3) zawiera 1 (stała o nazwie ARP_request), to znaczy, że zostało odebrane zapytanie o adres MAC (niekoniecznie nasz). W przypadku, gdy w polu TPA (rysunek 3) występuje nasz adres IP, to oznacza, że zapytanie jest skierowane do nas. Na takie zapytanie należy odesłać odpowiedź (wywołanie funkcji SendARPResponse, pokazanej na listingu 6).

Listing 6.

static void SendARPResponse ( EMACInstanceRecT * InterfInstance ,
                              ARPHeaderRecT *    ARPPacked )
{
  PtrOSFrameRecT OutFrame ;
  UCHAR NumberStr [ 20 ] ;
  /*-------------------------------------------------------------------------*/
  UART0SendString ( ARPMessage1 ) ;
  IPToString ( NumberStr , ARPPacked -> SenderIPAddress ) ;
  UART0SendString ( NumberStr ) ;
  UART0SendString ( ARPMessage2 ) ;
  DisplayMAC ( ARPPacked -> SenderMACAddress ) ;
  UART0SendString ( ARPMessage3 ) ;
  UART0SendString ( ARPMessage4 ) ;
  WordToHexStr ( NumberStr , 4 , '0' , ARPPacked -> HardwareType ) ;
  UART0SendString ( NumberStr ) ;
  UART0SendString ( ARPMessage5 ) ;
  WordToHexStr ( NumberStr , 4 , '0' , ARPPacked -> ProtocolType ) ;
  UART0SendString ( NumberStr ) ;
  UART0SendString ( ARPMessage6 ) ;
  WordToHexStr ( NumberStr , 4 , '0' , ARPPacked -> AddressLength >> 8 ) ;
  UART0SendString ( NumberStr ) ;
  UART0SendString ( ARPMessage7 ) ;
  WordToHexStr ( NumberStr , 4 , '0' , ARPPacked -> AddressLength & 0xFF ) ;
  UART0SendString ( NumberStr ) ;
  UART0SendString ( ARPMessage8 ) ;
  WordToHexStr ( NumberStr , 4 , '0' , ARPPacked -> Operation ) ;
  UART0SendString ( NumberStr ) ;
  UART0SendString ( ARPMessage9 ) ;
  DisplayMAC ( ARPPacked -> SenderMACAddress ) ;
  UART0SendString ( ARPMessage10 ) ;
  IPToString ( NumberStr , ARPPacked -> SenderIPAddress ) ;
  UART0SendString ( NumberStr ) ;
  UART0SendString ( ARPMessage11 ) ;
  DisplayMAC ( ARPPacked -> TargetMACAddress ) ;
  UART0SendString ( ARPMessage12 ) ;
  IPToString ( NumberStr , ARPPacked -> TargetIPAddress ) ;
  UART0SendString ( NumberStr ) ;
  UART0SendString ( ARPMessage13 ) ;
  OutFrame = InterfInstance -> InterfaceAllocFrameMemory ( sizeof ( OSFrameRecT ) ) ;
  if ( OutFrame )
  {
    OutFrame -> Index = 0 ;
    StartEthFrame ( OutFrame , ARPPacked -> SenderMACAddress , InterfInstance -> InterfaceMACAddress ,
                    FRAME_ARP ) ;
    AddUSHORTSwapField ( OutFrame , EthHardwType ) ;
    AddUSHORTSwapField ( OutFrame , FRAME_IP ) ;
    AddUSHORTSwapField ( OutFrame , IP_HLen_PLen ) ;
    AddUSHORTSwapField ( OutFrame , ARP_response ) ;
    AddByteArrayField ( OutFrame , InterfInstance -> InterfaceMACAddress , EthMACAddressSize ) ;
    AddULONGSwapField ( OutFrame , MachineIPAddress ) ;
    AddByteArrayField ( OutFrame , ARPPacked -> SenderMACAddress , EthMACAddressSize ) ;
    AddULONGSwapField ( OutFrame , ARPPacked -> SenderIPAddress ) ;
    InterfInstance -> InterfaceSendFrame ( OutFrame ) ;
    InterfInstance -> InterfaceDeallocFrameMemory ( OutFrame ) ;
  } /* if */ ;
} /* SendARPResponse */

Tym razem my jesteśmy stacją źródłową, a komputer, który przysłał nam zapytanie ARP, jest stacją docelową, czyli pole SHA zawiera nasz adres MAC, pole SPA zawiera nasz adres IP, pole THA zawiera adres MAC komputera pytającego oraz TPA zawiera adres IP komputera pytającego (dane adresowe komputera pytającego wynikają z odebranego pakietu ARP, dane adresowe „nasze” są znane z definicji).

Po zaprogramowaniu mikrokontrolera można poczynić kilka interesujących eksperymentów. Do „zabawy” zostanie użyte okienko z liniami poleceń tekstowych (CMD.EXE). Polecenie PING 192.168.0.55 generuje wysłanie pakietu do zbadania drożności połączenia. Test drożności jest skierowany z komputera o adresie IP=192.168.0.68 do maszyny o adresie IP=192.168.0.55 (do modułu z LPC2378). Ponieważ system Windows początkowo nie zna docelowego adresu MAC, przesłanie PING zostanie poprzedzone zapytaniem ARP. Nasz program demonstracyjny zawiera obsługę zapytań ARP, toteż Windows szybko pozna adres MAC skojarzony z adresem IP 192.168.0.55. Po otrzymaniu odpowiedzi ARP, Windows wyśle sygnał do badania drożności połączenia. Jest oczywiste, że nikt na ten sygnał nie odpowie (LPC2378 nie zawiera takiej funkcjonalności), więc po czterech próbach Windows zakończy test z informacją, że nie odebrał żadnego sygnału odpowiedzi w teście połączenia, co nie powinno nikogo dziwić. Natomiast można wydać kolejne polecenie dla Windows (ARP-a), które poleca wyświetlić aktualną listę powiązań występującą w Windows (rysunek 6).

Rysunek 6.

Z wyświetlonej informacji wynika, że Windows otrzymało poprawną odpowiedź na zapytanie (zna nasz adres MAC). Od tej chwili każde kolejne polecenie PING nie będzie już generować zapytań ARP (mikrokontroler LPC2378 nie wyśle w kanał szeregowy informacji o odebraniu pakietu ARP, jak pokazuje rysunek 7; do podglądania, co się dzieje w mikrokontrolerze LPC2378, został napisany specjalny program terminal.exe, który można pobrać jako materiały dodatkowe). Możliwe jest usunięcie z pamięci Windows wpisów łączących adresy IP z adresami MAC (polecenie ARP-d), po którym kolejny PING ponownie zostanie poprzedzony zapytaniem ARP lub odczekanie odpowiedniego czasu na „autonomiczne” usunięcie wpisu.

Rysunek 7.

Z informacji wyświetlonej przez LPC2378, wynika, że... wszystko „zgadza się na sztuki” z tym, co jest wyżej opisane. Komputer (IP=192.168.0.68, MAC=00-18-7D-0A-6E-25) wysłał zapytanie ARP do modułu ethernetowego (IP=192,168.0.55).

Pakiet zapytania jest wysłany jako rozgłoszeniowy (ma adres odbiorcy MAC=FF-FF-FF-FF-FF-FF). Istotne pola identyfikujące sieć mają właściwą wartość (HTYPE, PTYPE, HLEN, PLEN), pole OPER zawiera 1 (kod zapytania ARP). Pole SHA i SPA wskazuje na komputer, natomiast pole THA i TPA wskazuje na moduł (ponieważ komputer nie znał adresu MAC modułu, pole TPA jest wyzerowane). Wysłanie sygnału PING pod inny (nawet nieistniejący) adres, ponownie generuje w Windows zapytanie ARP. Jest oczywistym, że dotrze ono również do naszego modułu, jednak poza wyświetleniem informacji, że został odebrany pakiet ARP, nie generuje to żadnych działań (zapytanie nie dotyczy nas). Pokazuje to rysunek 8.

Rysunek 8.

Powyższy program prezentuje „algorytm” odpowiedzi na zapytanie ARP. Warto rozpatrzeć drugi wariant protokołu ARP, zapytanie generowane przez mikrokontroler LPC2378. W tym celu został utworzony kolejny program. Jego zadaniem jest wczytać via kanał szeregowy adres IP i wygenerować zapytanie ARP do sieci lokalnej. Program w zakresie obsługi sieci nie różni się od poprzedniego. Utworzone instancje nadal obsługują jedynie protokół ARP. Obsługa odpowiedzi na zapytania ma już typową postać, jest obsługiwana „po cichu”, nie generując żadnych informacji na ekran terminalu. Główna uwaga jest skierowana tym razem na utworzenie pakietu ARP i wysłanie go w sieć lokalną.

Program w tym celu jest rozbudowany o obsługę wejścia szeregowego jak pokazuje listing 7 (funkcja SerialPool jest wywoływana w pętli głównej programu).

Listing 7.

UCHAR TraceInput1 [ ]       = "\r\nWczytano: " ;
UCHAR TraceInput2 [ ]       = "\r\n" ;

static void SerialPool ( void )
{
  UCHAR Ch ;
  ULONG DersinationIP ;
  /*-------------------------------------------------------------------------*/
  if ( UART0DataPresent ( ) )
  {
    Ch = UART0GetData ( ) ;
    if ( Ch == 0x0D )
    {
      UART0SendString ( TraceInput1 ) ;
      UART0SendString ( ARPDemoInstance . Buffer ) ;
      UART0SendString ( TraceInput2 ) ;     
      StrToIP ( ARPDemoInstance . Buffer , & DersinationIP ) ;
      PrepareARPRequest ( ARPDemoInstance . EMACLayer , DersinationIP ) ;
      ARPDemoInstance . Index = 0 ;
      ARPDemoInstance . Buffer [ 0 ] = 0 ;
      return ;
    } /* if */ ;
    if ( ARPDemoInstance . Index < BufferSize )
    {
      ARPDemoInstance . Buffer [ ARPDemoInstance . Index ] = Ch ;\
      ARPDemoInstance . Index ++ ;
      ARPDemoInstance . Buffer [ ARPDemoInstance . Index ] = 0 ;
    } /* if */ ;
  } /* if */ ;
} /* SerialPool */

Kompletuje ona z odbieranych znaków bufor zawierający tekstową postać adresu IP, dla którego wystosowane będzie zapytanie o adres MAC. W sytuacji napotkania w strumieniu przychodzących z UART0 znaku o kodzie 0D hex (odpowiadający znakowi Enter), program dokona konwersji do postaci binarnej (adres IP jest liczbą 32-bitową) i wygeneruje zapytanie ARP (wywołanie funkcji PrepareARPRequest). Kolejna funkcja jest pokazana na listingu 8.

Listing 8.

UCHAR ARPMessage4 [ ]        = "\r\nWysylamy zapytanie o MAC do maszyny o IP=" ;
UCHAR ARPMessage5 [ ]        = "\r\n" ;

void PrepareARPRequest ( EMACInstanceRecT * InterfInstance ,
                         ULONG              DestIPAddress )
{
  PtrOSFrameRecT OutFrame ;
  UCHAR DestMACAddress [ EthMACAddressSize ] ;
  ULONG Loop ;
  UCHAR NumberStr [ 20 ] ;
  /*-------------------------------------------------------------------------*/
  UART0SendString ( ARPMessage4 ) ;
  IPToString ( NumberStr , DestIPAddress ) ;
  UART0SendString ( NumberStr ) ;
  UART0SendString ( ARPMessage5 ) ;
  OutFrame = InterfInstance -> InterfaceAllocFrameMemory ( sizeof ( OSFrameRecT ) ) ;
  if ( OutFrame )
  {
    OutFrame -> Index = 0 ;
    for ( Loop = 0 ; Loop < EthMACAddressSize ; Loop ++ )
      DestMACAddress [ Loop ] = 0 ;
    StartEthFrame ( OutFrame , Broadcast , InterfInstance -> InterfaceMACAddress ,
                    FRAME_ARP ) ;
    AddUSHORTSwapField ( OutFrame , EthHardwType ) ;
    AddUSHORTSwapField ( OutFrame , FRAME_IP ) ;
    AddUSHORTSwapField ( OutFrame , IP_HLen_PLen ) ;
    AddUSHORTSwapField ( OutFrame , ARP_request ) ;
    AddByteArrayField ( OutFrame , InterfInstance -> InterfaceMACAddress , EthMACAddressSize ) ;
    AddULONGSwapField ( OutFrame , MachineIPAddress ) ;
    AddByteArrayField ( OutFrame , DestMACAddress , EthMACAddressSize ) ;
    AddULONGSwapField ( OutFrame , DestIPAddress ) ;
    InterfInstance -> InterfaceSendFrame ( OutFrame ) ;
    InterfInstance -> InterfaceDeallocFrameMemory ( OutFrame ) ;
  } /* if */ ;
} /* PrepareARPRequest */

Alokuje ona obszar pamięci na pakiet ARP, który jest następnie wypełniony odpowiednimi danymi. Tu warto zwrócić uwagę, że tworząc nagłówek ethernetowy (wypełniając pola samego nagłówka, jak pokazano na rysunku 2), jako adres MAC odbiorcy wstawiona jest wartość FF-FF-FF-FF-FF-FF (stała o nazwie Broadcast), co w rzeczywistości oznacza, że tworzony pakiet jest adresowany do wszystkich (adres MAC jest rozgłoszeniowy i wysyłana ramka ethernetowa niosąca w sobie pakiet zapytania ARP ma trafić do wszystkich w obrębie sieci lokalnej). Ta operacja jest realizowana wewnątrz funkcji StartEthFrame. W dalszej kolejności za tym nagłówkiem wstawione są dane wymagane przez strukturę pakietu ARP (rysunek 3). Akcja jest zakończona wysłaniem wygenerowanego pakietu (InterfInstance->InterfaceSendFrame) i zwolnieniem przydzielonej pamięci. Na taki pakiet należy rzecz jasna oczekiwać odpowiedzi (no chyba, że wprowadzony docelowy adres nie istnieje w sieci lokalnej). Jego pojawienie się zostanie odebrane i w obsłudze odebranego pakietu (funkcja  RecvARPResponse, listing 5) zostanie zdekodowane. W wyniku rozpoznania pakietu odpowiedzi ARP zostanie wywołana funkcja RecvARPResponse (listing 9).

Listing 9.

UCHAR ARPMessage1 [ ]  = "\r\nOtrzymalismy odpowiedz na zapytanie ARP\r\nMaszyna o IP=" ;
UCHAR ARPMessage2 [ ]  = " ma MAC=" ;
UCHAR ARPMessage3 [ ]  = "\r\n" ;

static void RecvARPResponse ( EMACInstanceRecT * InterfInstance ,
                              ARPHeaderRecT *    ARPPacked )
{
  UCHAR NumberStr [ 20 ] ;
  /*-------------------------------------------------------------------------*/
  UART0SendString ( ARPMessage1 ) ;
  IPToString ( NumberStr , ARPPacked -> SenderIPAddress ) ;
  UART0SendString ( NumberStr ) ;
  UART0SendString ( ARPMessage2 ) ;
  DisplayMAC ( ARPPacked -> SenderMACAddress ) ;
  UART0SendString ( ARPMessage3 ) ;
} /* RecvARPResponse */

Jej zadaniem jest wyświetlenie informacji na ekranie terminalu, byś mógł, drogi Czytelniku, na własne oczy ujrzeć, że obsługa protokołu ARP jako wstęp do obsługi sieci wcale nie jest jakimś niepojętym zagadnieniem. Tu być może nie odkryję nic nowego, twierdząc, że jak się zrozumie „przeciwnika”, to wcale nie jest on taki straszny, na jakiego wygląda. Właściwie to wszelkie problemy są do pokonania, zawsze znajdzie się pomocna dłoń (choćby moja), która pozwoli poznać realny świat, która zainspiruje do własnych doświadczeń, bo wiedza jest bezcenna (cóż, znów nic nowego). Wracając do tematu, po uruchomieniu modułu ethernetowego oraz odpowiedniego programu w komputerze PC, którego zadaniem jest umożliwienie komunikacji z modułem poprzez kanał transmisji szeregowej, powstaje możliwość „wydania” polecenia dla mikrokonrolera. Sprowadza się ono do wpisania w okienku Dane adresu IP, dla którego ma być pozyskany adres MAC i kliknięcia na przycisk Wyślij. Tradycyjnie wpisuję adres własnego komputera: 192.168.0.68. Spowodowało to wysłanie zapytania do sieci i uzyskanie zwrotnej informacji o adresie MAC. Ponownie wszystko się zgadza, jak już wiadomo, mój komputer o podanym adresie ma MAC= 00-18-7D-0A-6E-25. W sieci lokalnej znajduje się jeszcze jedna stacja o adresie IP=192.168.0.254 (jej adres MAC=24-00-BA-BB-9D-E1), będąca moim routerem do sieci rozległej (czego nie uwzględnia rysunek 4). Wpisując w polu Dane ten adres IP, program demonstracyjny „uzyska” adres MAC routera (pokazuje to rysunek 9).

Rysunek 9.

W ten sposób można zapytać o adres MAC każdej stacji w obrębie sieci lokalnej.

Na stronach elportal.pl dostępne są następujące materiały dodatkowe:

  • Poslugiwanie sie programem FLASHMAGIC.pdf – dokument opisujący posługiwanie się programem FLASHMAGIC,
  • Obsluga_uart_w_przerwaniach.pdf – dokument opisujący obsługę wszystkich podzespołów UART w mikrokontrolerze LPC,
  • Poslugiwanie sie srodowiskiem uVision.pdf – dokument opisujący posługiwanie się oprogramowaniem narzędziowym do tworzenia programów dla mikrokontrolera LPC,
  • UART0, UART1, UART2, UART3 – gotowe przykładowe programy do obsługi poszczególnych układów UART,
  • ARP01, ARP02 – opisane w artykule programy prezentujące protokół ARP,
  • TERMINAL – program dla komputera PC emulatora terminalu w wersji dla Windows i Linux (z podziękowaniami dla Nataszy Biecek za wsparcie w uzyskaniu programu dla Linuxa).
Do pobrania
Download icon Filozofia sieci. Protokół ARP
Firma:
Tematyka materiału: Protokół ARP, Address Resolution Protocol, HTYPE, PTYPE, HLEN, PLEN, OPER, SHA, SPA, THA, TPA, PING, MAC, LPC2378, FLASHMAGIC, UART, LPC, uVision, Terminal
AUTOR
Źródło
Elektronika dla Wszystkich maj 2020
Udostępnij
Zobacz wszystkie quizy
Quiz weekendowy
Edukacja
1/10 Jak działa rezystor LDR?
Oceń najnowsze wydanie EdW
Wypełnij ankietę i odbierz prezent
W tym numerze znajdziesz źródłową wersję artykułu publikowanego obok
Elektronika dla Wszystkich
maj 2020
Elektronika dla Wszystkich
Przejrzyj i kup
UK Logo
Elektronika dla Wszystkich
Zapisując się na nasz newsletter możesz otrzymać GRATIS
najnowsze e-wydanie magazynu "Elektronika dla Wszystkich"