2013-06-12 11 views
9

Pracuję nad modelowaniem różnych aspektów sieci w bazie danych. Jednym z bardziej irytujących problemów, z którymi mamy do czynienia, jest tworzenie zakresów podsieci, a następnie ustalenie, czy dany zbiór adresów IP mieści się w tych zakresach. Nasz obecny model uwzględnia różnic między IPv4 i IPv6 z następującymi kolumnami:Biorąc pod uwagę zakres podsieci i listę adresów IP, zaznacz wszystkie wiersze, w których adresy IP mieszczą się między

[subnet_sk]  [int] IDENTITY(1,1) NOT NULL, 
[ipv6_network] [char](39)   NULL, 
[ipv6_broadcast] [char](39)   NULL, 
[ipv4_network] [char](15)   NULL, 
[ipv4_broadcast] [char](15)   NULL, 
[network_type] [char](4)   NOT NULL 

Powyższy schemat sprawia, że ​​kilka założeń, które mają podkreślić. Wykorzystujemy w pełni rozwinięte adresy IP (192.168.001.001 vs. 192.168.1.1) do przechowywania i porównywania. Podjęliśmy tę decyzję z powodu problemów związanych z przechowywaniem IPv6 addresses numerically in SQL server (bigints są niepodpisane, co oznacza, że ​​musielibyśmy użyć sześciu kolumn do reprezentowania IPv6).

Biorąc pod uwagę ten schemat tabeli jest dość łatwe do napisania jednego OFF Wybierz sprawozdania do określenia, czy IP dowolnego typu jest między zakresami w tabeli:

select * 
    from subnet 
where '1234:0000:0000:0000:fc12:00ab:0042:1050' 
     between ipv6_network 
      and ipv6_broadcast 

-- or alternatively for IPv4 

select * 
    from subnet 
where '192.168.005.015' 
     between ipv4_network 
      and ipv4_broadcast 

Co jest trudniejsze jest podana lista Adresy IP określają, które z nich znajdują się pomiędzy zakresami podsieci. Lista adresów IP zostanie dostarczona przez użytkownika i będzie przechowywana w bazie danych w postaci , a nie. Oczywiście dla danych przechowywanych w bazie danych mogę zrobić podobne sprzężenie jak w poniższym przykładzie. Na przykład użytkownik może podać 1234:0000:0000:0000:fc12:00ab:0042:1050, 192.168.001.001 i 192.168.1.1. Jedynym rozwiązaniem mam wymyślić jest użycie table-valued function to split a list adresów IP i wykonać za pomocą sprzężenia pomiędzy:

-- this only covers the IPv4 addresses from the above list a similar query would 
-- be used for IPv6 and the two queries could be unioned 
select sub.* 
    from fn_SplitList('192.168.001.001,192.168.005.015',',') split 
     join subnet sub 
     on split.Data 
      between sub.ipv4_network 
       and sub.ipv4_broadcast 

przy wykorzystaniu funkcji podziału działa czuje hacky. Spędziłem większą część poranka wąchając około common table expressions, ale nie mogłem wymyślić implementacji, która by działała. Najlepiej by było gdyby jeden wybrał, czy odrzucić dany ciąg z kolumn IPv4 lub IPv6, ale jeśli to nie jest możliwe, mogę oddzielić listę przed przekazaniem kolekcji adresów IP do bazy danych.

Aby ułatwić odpowiedź, utworzyłem SQL Fiddle powyższych. Czy istnieje mechanizm w SQL (wolałbym nie używać T-SQL) z podaniem listy adresów IP, aby określić, które z istniejących zakresów podsieci znajdują się pomiędzy tymi adresami? Czy powyższy schemat jest nawet właściwym podejściem do problemu, gdyby inny model danych doprowadził do łatwiejszego rozwiązania?

+0

nie mam dobrą znajomość sieci. Chciałbym wiedzieć, że tylko fn_SplitList() przeszkadza ci lub chcesz znaleźć lepszy sposób na obsłużenie całego scenariusza? –

+0

@RaviSingh biorąc pod uwagę ten model danych miałbym to samo pytanie, gdybyśmy pracowali z listą dat, próbując ustalić, czy mieszczą się w zakresie. Więc moim problemem jest to, że czuję, że fn_SplitList jest sposobem obejścia problemu, ale wydaje się, że istnieje czystsze rozwiązanie. To powiedziawszy, jeśli powiesz mi "twój schemat jest błędny, a twoje zapytanie byłoby łatwiejsze, gdyby twój model danych był skonfigurowany w ten sposób," Byłbym wszystkimi uszami. Więc jeśli nie możesz pomóc w modelowaniu, ale możesz pomóc w lepszym zapytaniu, które nie używa fn_SplitList, jestem bardzo zainteresowany twoim rozwiązaniem. – ahsteele

+0

Natknęliśmy się na to późno, ale dla odniesienia tutaj są niektóre podobne wątki omawiające podziały stringów w SQL Server: http://stackoverflow.com/questions/2647/split-string-in-sql i http://stackoverflow.com/ pytania/314824/t-sql-przeciwny-do-ciąg-konkatenacja-jak-podzielony-na-wiele-reco – rutter

Odpowiedz

1

To nie jest kompletne rozwiązanie, ale bardziej pomysł na inny projekt, Myślałem zamiast wykonywania typowego porównania SQL, dlaczego nie spróbować użyć porównania logicznego. Wiedząc niewiele realizacji sql Próbowałem wygłupiać z porównania mnożenie (z bigint)

istnieje wiele optymalizacja do zrobienia, ale myślę, że istnieje możliwość, że może to pomóc,

mała demonstracja, w której porównałem 4 ip (192.168.1.1 i 3 więcej), i używać ich jako bigints spowodować int jest zbyt mała, a trzeba użyć logicznego porównania bitowe, (więcej informacji tutaj http://msdn.microsoft.com/en-us/library/ms174965.aspx)

select * from (
    select cast(192168001001 as bigint) as ip union all 
    select cast(192168001002 as bigint) as ip union all 
    select cast(192168002001 as bigint) as ip union all 
    select cast(192168002002 as bigint) as ip 
) as ip_table 
where ip & cast(192168001000 as bigint) = cast(192168001000 as bigint) 

jak widać I (i/&) adres IP i adres sieciowy , a następnie porównać to do adresu sieciowego, jeśli jest zgodny, to jest w tym zakresie

poprawić mnie, jeśli się mylę, muszę myśleć o tym, bardzo interesujące Rzeczywiście

Result

Edytuj: Edytuj poniżej: bigint jest za mały dla IPv6, , więc to niestety nie działa, operacja bitowa (AND) nie może być wykonana z binarnym typem danych, akceptuje tylko typy całkowite ...

+1

bigint działa naprawdę dobrze dla adresów IPv4. Niestety SQL Server nie zezwala na niepodpisane bigify, co utrudnia porównywanie bitowe dla IPv6, ponieważ potrzebujesz trzech kolumn do reprezentowania adresu IPv6 jako bigint. – ahsteele

1

I zostały patrząc na SQL Fiddle, zabawy z kwerendy w pytaniu

być w 100% jasne, trzeba zapytać, aby znaleźć wszystkie zakresy, że lista adresów hosta podlega.

więc można działać tak, jakby gospodarze znajduje się lista/tabela z danymi, a następnie sprzężenia wewnętrznego podsieci na nim (lub lewej przyłączyć, jeśli chcesz go do stawienia się nawet bez podsieci)

select * 
from (
    select '192.168.001.001' as ip union 
    select'192.168.005.015') as hosts 
inner join subnet 
    on ip between ipv4_network and ipv4_broadcast 

mam 4 wyniki (istniały dwie podsieci, które pasowały do ​​każdego rekordu)

1

Czy rozważałeś przechowywanie zarówno formatów ipv6, jak i ipv4 w jednej kolumnie?

Storing IP addresses in Microsoft SQL Server

To wymagałoby konwersję dowolnych danych źródłowych dla ciebie porównania (lub w drugą stronę), ale przynajmniej można uniknąć konieczności dwóch oddzielnych zapytań do sprawdzenia.

Byłbym skłonny utworzyć WRT z danych źródłowych (FOR XML?), A następnie dołączyć do tabeli bazy danych (podsieci).

0

Chciałbym rozwiązać ten problem, używając [xml] lub [heirarchyid] (http://technet.microsoft.com/en-us/library/bb677290.aspx) i traktować dane jako drzewo. Staje się względnie prosty, a następnie buduje drzewo na podstawie istniejących podsieci za pomocą metody [drzewo]. [Uruchom] @netub, która uruchamia drzewo i znajduje węzeł pasujący do @subnet. Traktując dane jako drzewo (które faktycznie jest) i budując wspólne metody obsługi drzewa (rekurencyjne), powinieneś być w stanie łatwo dostać się do punktu, w którym znajduje się węzeł, jeśli istnieje lub wstawić go i uzyskać następny i wcześniejsze węzły.

Mogę podać bardziej szczegółowe przykłady, jeśli jest to interesujące, ale nie jest to trywialne rozwiązanie, więc nie zamierzam poświęcać czasu na to inaczej. Pokazuję tutaj prosty prototyp, który znajduje węzeł będący rodzicem (jako proste dopasowanie) maski wejściowej. Podaję to tylko jako przykład, ale jeśli rozwiązanie Cię interesuje, mogę podać więcej szczegółów lub możesz łatwo zobaczyć, jak zbudować rozwiązanie za pomocą tych technik.

Peace, Katherine

use [test_01]; 

    go 

    if schema_id(N'tree') is null 
     execute (N'create schema tree'); 

    go 

    if object_id(N'[tree].[run]', 
       N'FN') is not null 
     drop function [tree].[run]; 

    go 

    create function [tree].[run] (
     @network  [xml], 
     @mask_to_find [sysname], 
     @position  [sysname] 
    ) 
    returns [sysname] 
    as 
     begin 
      declare @quad [sysname] = substring(@mask_to_find, 
         0, 
         charindex(N'.', 
            @mask_to_find, 
            0)); 

      set @mask_to_find = substring(@mask_to_find, 
              charindex(N'.', @mask_to_find, 0) + 1, 
              len(@mask_to_find)); 
      set @network = @network.query('/*[@quad=sql:variable("@quad")]/*'); 

      if(@network.value('count (/*)', 
           'int') > 0) 
       begin 
        set @position = coalesce(@position + N'.', N'') + @quad; 
       end 
      else 
       set @position = coalesce(@position + N'.', N'') + N'000'; 

      if (@@nestlevel < 4) 
       return [tree].[run] (@network, 
            @mask_to_find, 
            @position); 

      return @position; 
     end 

    go 

    declare @network [xml] = N'<subnet quad="255" > 
      <subnet quad="255" > 
       <subnet quad="192" /> 
       <subnet quad="255" /> 
      </subnet> 
     </subnet> 
     <subnet quad="10" />'; 
    declare @mask_to_find [sysname] = N'255.255.190.000'; 
    declare @position [sysname]; 

    select [tree].[run] (@network, 
         @mask_to_find, 
         @position) 

    go 
Powiązane problemy