понедельник, 8 марта 2010 г.

NTLM Authentication просто и со вкусом

Я довольно долго искал простой и стандартный путь для того чтобы произвести аутентификацию пользователя (текущий пользователь - вы не знаете его пароля и пользователь заданный при помощи NetworkCredential) на удаленном компьютере. Хотел уже было все написать ручками через AcquireCredentialsHandle/InitializeSecurityContext/AcceptSecurityContext/CompleteAuthToken, но совсем неожиданно нашел NegotiateStream - который и занимается данным непростым процессом. По сему поводу написал маленький примерчик (пример работает только в пределах одного компьютера, если хотите большего, то замените NamedPipeXxxStream на Socket).

  1. namespace AuthTest
  2. {
  3.   public static class Constants
  4.   {
  5.     public const string PipeName = "AuthTest";
  6.   }
  7. }

  1. using System;
  2. using System.IO.Pipes;
  3. using System.Net;
  4. using System.Net.Security;
  5. using System.Security.Principal;
  6.  
  7. namespace AuthTest.Client
  8. {
  9.   internal class Program
  10.   {
  11.     private static void Main(string[] args)
  12.     {
  13.       try
  14.       {
  15.         Console.WriteLine("AuthTest Client Copyright (C) 2010 Mikhail Pilin");
  16.         using (var stream = new NamedPipeClientStream(Constants.PipeName))
  17.         {
  18.           stream.Connect();
  19.           using (var negotiateStream = new NegotiateStream(stream, true))
  20.             negotiateStream.AuthenticateAsClient(
  21.               GetCredential(args),
  22.               "",
  23.               ProtectionLevel.EncryptAndSign,
  24.               TokenImpersonationLevel.Delegation);
  25.         }
  26.  
  27.         Console.WriteLine("Done");
  28.       }
  29.       catch (Exception e)
  30.       {
  31.         Console.Error.WriteLine(e);
  32.       }
  33.     }
  34.  
  35.     private static NetworkCredential GetCredential(string[] args)
  36.     {
  37.       switch (args.Length)
  38.       {
  39.       case 0:
  40.         return (NetworkCredential) CredentialCache.DefaultCredentials;
  41.       case 1:
  42.         return new NetworkCredential(args[0], "");
  43.       case 2:
  44.         return new NetworkCredential(args[0], args[1]);
  45.       case 3:
  46.         return new NetworkCredential(args[0], args[1], args[2]);
  47.       default:
  48.         throw new ArgumentException();
  49.       }
  50.     }
  51.   }
  52. }

  1. using System;
  2. using System.IO.Pipes;
  3. using System.Net.Security;
  4. using System.Security.Principal;
  5.  
  6. namespace AuthTest.Server
  7. {
  8.   internal class Program
  9.   {
  10.     private static void Main()
  11.     {
  12.       try
  13.       {
  14.         Console.WriteLine("AuthTest Server Copyright (C) 2010 Mikhail Pilin");
  15.         using (var stream = new NamedPipeServerStream(Constants.PipeName))
  16.         {
  17.           stream.WaitForConnection();
  18.           using (var negotiateStream = new NegotiateStream(stream, true))
  19.           {
  20.             negotiateStream.AuthenticateAsServer();
  21.             var remoteIdentity = (WindowsIdentity) negotiateStream.RemoteIdentity;
  22.             Console.WriteLine("Name: {0}", remoteIdentity.Name);
  23.             Console.WriteLine("User: {0}", remoteIdentity.User);
  24.             Console.WriteLine("AuthenticationType: {0}", remoteIdentity.AuthenticationType);
  25.             Console.WriteLine("ImpersonationLevel: {0}", remoteIdentity.ImpersonationLevel);
  26.             Console.WriteLine("IsAuthenticated: {0}", remoteIdentity.IsAuthenticated);
  27.             Console.WriteLine("IsAnonymous: {0}", remoteIdentity.IsAnonymous);
  28.             Console.WriteLine("IsSystem: {0}", remoteIdentity.IsSystem);
  29.             Console.WriteLine("IsGuest: {0}", remoteIdentity.IsGuest);
  30.           }
  31.         }
  32.         Console.WriteLine("Done");
  33.       }
  34.       catch (Exception e)
  35.       {
  36.         Console.Error.WriteLine(e);
  37.       }
  38.     }
  39.   }
  40. }

среда, 3 марта 2010 г.

Object synchronization method was called from an unsynchronized block of code

Симптомы: Посредством remoting вы создаете Mutex на удаленной машине все работает хорошо, но в один прекрасный момент при попытке вызвать ReleaseMutex вы получаете exception "Object synchronization method was called from an unsynchronized block of code". Ошибка плавающая, возникает время от времени и не на всех компьютерах.

Диагноз: ReleaseMutex вызывается не из того же thread что и вызов WaitOne так как никто не гарантирует, что отдельные remoting вызовы будут работать в том же thread.

Лечение: Можно нагородить на удаленной машине объект, который будет обеспечивать тот же тред для вызывов WaitOne и ReleaseMutex. А можно просто заменить Mutex на Semaphore...

понедельник, 15 февраля 2010 г.

Выравнивание в CLR и в C/C++

Столкнулся вот с проблемой: нужно было установить однозначное соответствие структуры в C/C++ и в CLR, структурка небольшая вот такого вида:

  1. #pragma pack(push, PPP)
  2. struct XXX
  3. {
  4.     LPVOID m_AAA;
  5.     ULONG m_BBB;
  6.     ULONG m_CCC;
  7.     ULONG m_DDD;
  8. };
  9. #pragma pack(pop)

Где PPP я установил в 4 для x86 и в 8 для x64. Другими словами каждое поле начинается со смещения кратного размеру указателя. С другой стороны, в CLR, я определил структуру примерно таким образом:

  1. static mdToken const g_NilImplements[] = {mdTokenNil};
  2. mdTypeDef stk;
  3.  
  4. if (hr = mde->DefineTypeDef(L"XXX", tdNotPublic | tdSequentialLayout | tdClass | tdSealed | tdBeforeFieldInit | tdAnsiClass, valueTypeToken, const_cast<mdToken *>(g_NilImplements), &stk), FAILED(hr))
  5.     throw _hr_error("Can't emit inject structure", hr);
  6.  
  7. if (hr = mde->SetClassLayout(stk, PPP, NULL, 0), FAILED(hr))
  8.     throw _hr_error("Can't emit inject structure", hr);
  9.  
  10. static BYTE const g_fblobPtr[] =
  11. {
  12.     IMAGE_CEE_CS_CALLCONV_FIELD,
  13.     ELEMENT_TYPE_PTR, ELEMENT_TYPE_VOID,
  14. };
  15.  
  16. mdFieldToken ftkAAA;
  17.  
  18. if (hr = mde->DefineField(stk, L"AAA", fdPublic, g_fblobPtr, sizeof(g_fblobPtr) / sizeof(*g_fblobPtr), ELEMENT_TYPE_END, NULL, 0, &ftkAAA), FAILED(hr))
  19.     throw _hr_error("Can't emit field structure", hr);
  20.  
  21. static BYTE const g_fblobUInt32[] =
  22. {
  23.     IMAGE_CEE_CS_CALLCONV_FIELD,
  24.     ELEMENT_TYPE_U4,
  25. };
  26.  
  27. mdFieldToken ftkBBB;
  28.  
  29. if (hr = mde->DefineField(stk, L"BBB", fdPublic, g_fblobUInt32, sizeof(g_fblobUInt32) / sizeof(*g_fblobUInt32), ELEMENT_TYPE_END, NULL, 0, &ftkBBB), FAILED(hr))
  30.     throw _hr_error("Can't emit field structure", hr);
  31.  
  32. mdFieldToken ftkCCC;
  33.  
  34. if (hr = mde->DefineField(stk, L"CCC", fdPublic, g_fblobUInt32, sizeof(g_fblobUInt32) / sizeof(*g_fblobUInt32), ELEMENT_TYPE_END, NULL, 0, &ftkCCC), FAILED(hr))
  35.     throw _hr_error("Can't emit field structure", hr);
  36.  
  37. mdFieldToken ftkDDD;
  38.  
  39. if (hr = mde->DefineField(stk, L"DDD", fdPublic, g_fblobUInt32, sizeof(g_fblobUInt32) / sizeof(*g_fblobUInt32), ELEMENT_TYPE_END, NULL, 0, &ftkDDD), FAILED(hr))
  40.     throw _hr_error("Can't emit field structure", hr);

Мир казался прекрасным под x86, но стоило запуститься под x64 структуры кардинально разъехались. После внимательного изучения документации выяснилось, что в CLR в отличии от C/C++ для выравнивания выбирается меньшее из двух чисел: размер самого поля и установленного через SetClassLayout выравнивания. Для устранения проблемы пришлось установить PPP в 1 для всех платформ. Хотя в принципе есть и другие решения, но я выбрал самое простое.

воскресенье, 31 января 2010 г.

Бережливой хозяйке на заметку

Все знают функцию QueryPerformanceCounter, но как давеча выяснилось не все знают, что ее реализация под Windows Vista x64 использует операции с плавающей арифметикой и соответственно портит регистр xmm0 после своего вызова. Windows7 x64 такой пакости не делает.

среда, 27 января 2010 г.

Нестыковочки

Неожиданно на одном компьютере dotTrace 4.0 начал валиться при профиляции безобидного приложения. Выяснилось, что виноват во всем класс System.Reflection.Emit.DynamicMethod. Оказывается при обработке исключений он сильно влияет на парность ExceptionSearchFunctionLeave и ExceptionUnwindFunctionLeave - вызовы ExceptionSearchFunctionEnter и ExceptionUnwindFunctionEnter напрочь отсутствуют. Профайлер не ожидавший такой подставы валится (в релизной сборке убрано большинство проверок для увеличения производительности). Сейчас все починено и работает...

P.S. Подробности по этой проблеме тут в разделе DynamicMethods.

Новое в dotTrace 4.0

Появилась поддержка Silverlight 4.0, пока не сделан отдельный тип аргумента, но я уже думаю об этом. Технически вся поддержка свелась к правильной установке CORECLR_ENABLE_PROFILING и CORECLR_PROFILER, прошу не путать с COR_ENABLE_PROFILING и COR_PROFILER.

вторник, 24 ноября 2009 г.

Поддержка .NET Framework 4.0 в dotTrace 4.0 (Шаг №2)

Неожиданно быстро (всего за один рабочий день) получилось сделать нормальную поддержку .NET Framework 4.0 (реализован ICorProfilerCallback3 и используется ICorProfilerInfo3 + Enter3/Leave3/Tailcall3) в dotTrace 4.0, правда пока не совсем полноценную:

  • нет поддержки нескольких .NET Framework'ов
  • не реализован режим attach to process
Все остальное воркает в лучшем виде.

воскресенье, 22 ноября 2009 г.

Поддержка .NET Framework 4.0 в dotTrace 4.0

По запросам дружественного проекта ReSharper был наконец вылизан режим совместимости с .NET Framework 4.0. Это конечно не есть полная поддержка нового фреймворка, но для начала это тоже неплохой шаг.