proxy
23 TopicsScaling Write Throughput in Azure Database for MySQL Using Application-Level Sharding
This blog post walks through scaling write throughput in Azure Database for MySQL using application level sharding. It starts with the why behind sharding and then builds a complete C# implementation that spreads writes across three Azure Database for MySQL Flexible Servers. Why Shard in the First Place? This post focuses specifically on scaling write throughput. A well-tuned single primary node can take you remarkably far, and techniques such as indexing strategies, write batching, redo log optimization, and vertical compute scaling each deliver real, lasting value. For many workloads, these optimizations are all you will ever need. That said, as write volume continues to grow, a single primary eventually approaches its practical capacity, and at that point the most durable way to keep scaling is to distribute the write workload across multiple primary instances. This architecture is what we call sharding. When you reach this inflection point, there are two primary patterns for managing multiple write nodes: Proxy or Middleware Layer Sharding: A sharding aware proxy sits between the application and a pool of Azure Database for MySQL instances, routing queries based on a shard key. While this abstracts the underlying topology from the application layer, it introduces an additional, complex component to operate, secure, scale, and patch. Application Layer Sharding: The application itself resolves the destination shard key and determines which of the N Azure Database for MySQL instances should receive a write before ever opening a database connection. Each backend target remains a completely standard, independent Azure Database for MySQL instance. This post explores the second approach. The core appeal of application layer sharding is architectural simplicity: it introduces zero infrastructure overhead and eliminates an extra network hop. Every shard behaves exactly like a standalone instance, meaning your existing backup, restore, monitoring pipelines, and the Azure portal function seamlessly without modification. The explicit tradeoff is that you forgo cross shard joins and distributed transactions in exchange for absolute predictability and control over data access patterns. The Plan We will build a small order management service that distributes its data across three Azure Database for MySQL instances that already exist. The application, written in C# on .NET 8, owns the partitioning logic. The premise: the three servers are already provisioned, the firewalls are configured, the network paths are established, and each server has its own administrative credentials. We are not provisioning infrastructure in this post. we are writing the application code that consumes it. mysql-shard-0.mysql.database.azure.com user: shard0_admin pwd: <secret-0> mysql-shard-1.mysql.database.azure.com user: shard1_admin pwd: <secret-1> mysql-shard-2.mysql.database.azure.com user: shard2_admin pwd: <secret-2> Each server hosts an identical appdb database with the same schema: CREATE TABLE users ( user_id BIGINT NOT NULL PRIMARY KEY, email VARCHAR(255) NOT NULL, created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, UNIQUE KEY uq_email (email) ); CREATE TABLE orders ( order_id BIGINT NOT NULL PRIMARY KEY, user_id BIGINT NOT NULL, amount_cents INT NOT NULL, created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, KEY ix_user (user_id) ); Two design decisions in this schema warrant explanation: No AUTO_INCREMENT for user_id or order_id. Two shards would otherwise generate the same value 42 independently. Instead, we assign identifiers in the application, using a scheme such as Snowflake, ULID, or UUIDv7. orders carries user_id, and we route by it. This is the single most important rule of sharding: choose a shard key that keeps related data colocated, so that the common queries remain on a single shard. A note on UNIQUE KEY uq_email. A unique index enforces uniqueness only within a single physical shard. Because we route by user_id, two users with different IDs and the same email may land on different shards, and both inserts will succeed. If you require globally unique emails, two options exist: (a) maintain a separate email → user_id lookup table on a single "directory" server and write to it first within an idempotent flow, or (b) shard the users table by a hash of email instead. We retain user_id routing throughout this post because it is the correct choice for orders, and we treat per shard email uniqueness as a best effort guard rather than a hard global invariant. How the Partitioning Works The naive approach to sharding is shard = hash(key) % N. This works until you need to add a fourth server, at which point roughly 75% of your data must move. In any system of meaningful size, that is prohibitively expensive. The established solution is virtual buckets. You hash the key into a large, fixed bucket space (here, 1024), then map buckets to physical shards. When you add capacity, you relocate only buckets; you never rehash the entire dataset. In production, the bucket_to_shard_map typically resides in a system such as Azure App Configuration or etcd, so that you can rebalance without redeploying. For this post, we keep it as an in-memory array seeded at startup, which is straightforward to replace later. The Project ShardingDemo/ ├── ShardingDemo.csproj ├── appsettings.json ├── Models.cs ├── ShardRouter.cs ├── UserRepository.cs └── Program.cs ShardingDemo.csproj <Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> <OutputType>Exe</OutputType> <TargetFramework>net8.0</TargetFramework> <Nullable>enable</Nullable> <ImplicitUsings>enable</ImplicitUsings> </PropertyGroup> <ItemGroup> <PackageReference Include="MySqlConnector" Version="2.6.0" /> <PackageReference Include="Microsoft.Extensions.Hosting" Version="8.0.0" /> <PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="8.0.0" /> </ItemGroup> <ItemGroup> <Content Include="appsettings.json" CopyToOutputDirectory="PreserveNewest" /> </ItemGroup> </Project> appsettings.json Shards is an ordered list, and a shard's position in the array is its logical ID. { "Shards": [ { "Host": "mysql-shard-0.mysql.database.azure.com", "Database": "appdb", "User": "shard0_admin", "Password": "REPLACE_ME_0" }, { "Host": "mysql-shard-1.mysql.database.azure.com", "Database": "appdb", "User": "shard1_admin", "Password": "REPLACE_ME_1" }, { "Host": "mysql-shard-2.mysql.database.azure.com", "Database": "appdb", "User": "shard2_admin", "Password": "REPLACE_ME_2" } ] } Models.cs namespace ShardingDemo; public sealed record User(long UserId, string Email, DateTime CreatedAt); public sealed record Order(long OrderId, long UserId, int AmountCents, DateTime CreatedAt); public sealed class ShardConfig { public required string Host { get; init; } public required string Database { get; init; } public required string User { get; init; } public required string Password { get; init; } } ShardRouter.cs using System.Security.Cryptography; using System.Text; using MySqlConnector; namespace ShardingDemo; public sealed class Shard : IAsyncDisposable { public int Id { get; } public MySqlDataSource DataSource { get; } public Shard(int id, ShardConfig cfg) { Id = id; var csb = new MySqlConnectionStringBuilder { Server = cfg.Host, Port = 3306, Database = cfg.Database, UserID = cfg.User, Password = cfg.Password, SslMode = MySqlSslMode.Required, Pooling = true, MinimumPoolSize = 2, MaximumPoolSize = 100, ConnectionTimeout = 10, DefaultCommandTimeout = 30, }; DataSource = new MySqlDataSourceBuilder(csb.ConnectionString).Build(); } public ValueTask DisposeAsync() => DataSource.DisposeAsync(); } public sealed class ShardRouter : IAsyncDisposable { private const int VirtualBuckets = 1024; private readonly IReadOnlyList<Shard> _shards; private readonly int[] _bucketToShardId; public ShardRouter(IEnumerable<ShardConfig> configs) { _shards = configs.Select((c, i) => new Shard(i, c)).ToList(); // Even distribution. Replace with a map loaded from your control plane for live rebalancing. _bucketToShardId = new int[VirtualBuckets]; for (int i = 0; i < VirtualBuckets; i++) _bucketToShardId[i] = i % _shards.Count; } public IReadOnlyList<Shard> AllShards => _shards; private static int BucketFor(long shardKey) { byte[] hash = MD5.HashData(Encoding.ASCII.GetBytes(shardKey.ToString())); // Use the first byte pair as an unsigned value, then map it into the bucket space. int value = (hash[0] << 8) | hash[1]; return value % VirtualBuckets; } public Shard ShardForKey(long shardKey) { int bucket = BucketFor(shardKey); return _shards[_bucketToShardId[bucket]]; } public async ValueTask DisposeAsync() { foreach (var s in _shards) await s.DisposeAsync(); } } UserRepository.cs Observe that every per user method calls ShardForKey(userId), even when inserting an order. This is the colocation rule at work. An order and its owning user always reside on the same shard, so queries for a single user only ever reach one shard. Only the cross-shard aggregate (TotalRevenueCentsAsync) must fan out. using MySqlConnector; namespace ShardingDemo; public sealed class UserRepository { private readonly ShardRouter _router; public UserRepository(ShardRouter router) { _router = router; } public async Task CreateUserAsync(long userId, string email, CancellationToken ct = default) { var shard = _router.ShardForKey(userId); await using var conn = await shard.DataSource.OpenConnectionAsync(ct); await using var cmd = conn.CreateCommand(); cmd.CommandText = "INSERT INTO users (user_id, email) VALUES (@id, Email)"; cmd.Parameters.AddWithValue("@id", userId); cmd.Parameters.AddWithValue("@email", email); await cmd.ExecuteNonQueryAsync(ct); } public async Task<User?> GetUserAsync(long userId, CancellationToken ct = default) { var shard = _router.ShardForKey(userId); await using var conn = await shard.DataSource.OpenConnectionAsync(ct); await using var cmd = conn.CreateCommand(); cmd.CommandText = "SELECT user_id, email, created_at FROM users WHERE user_id = ID"; cmd.Parameters.AddWithValue("@id", userId); await using var reader = await cmd.ExecuteReaderAsync(ct); if (!await reader.ReadAsync(ct)) return null; return new User(reader.GetInt64(0), reader.GetString(1), reader.GetDateTime(2)); } public async Task AddOrderAsync(long orderId, long userId, int amountCents, CancellationToken ct = default) { // Routed by user_id, so orders colocate with their owning user. var shard = _router.ShardForKey(userId); await using var conn = await shard.DataSource.OpenConnectionAsync(ct); await using var cmd = conn.CreateCommand(); cmd.CommandText = """ INSERT INTO orders (order_id, user_id, amount_cents) VALUES (@oid, @uid, amt) """; cmd.Parameters.AddWithValue("@oid", orderId); cmd.Parameters.AddWithValue("@uid", userId); cmd.Parameters.AddWithValue("@amt", amountCents); await cmd.ExecuteNonQueryAsync(ct); } public async Task<IReadOnlyList<Order>> GetOrdersForUserAsync(long userId, CancellationToken ct = default) { var shard = _router.ShardForKey(userId); await using var conn = await shard.DataSource.OpenConnectionAsync(ct); await using var cmd = conn.CreateCommand(); cmd.CommandText = """ SELECT order_id, user_id, amount_cents, created_at FROM orders WHERE user_id = @uid """; cmd.Parameters.AddWithValue("@uid", userId); var list = new List<Order>(); await using var reader = await cmd.ExecuteReaderAsync(ct); while (await reader.ReadAsync(ct)) { list.Add(new Order( reader.GetInt64(0), reader.GetInt64(1), reader.GetInt32(2), reader.GetDateTime(3))); } return list; } /// <summary>Cross shard fanout.</summary> public async Task<long> TotalRevenueCentsAsync(CancellationToken ct = default) { var tasks = _router.AllShards.Select(async shard => { await using var conn = await shard.DataSource.OpenConnectionAsync(ct); await using var cmd = conn.CreateCommand(); cmd.CommandText = "SELECT COALESCE(SUM(amount_cents), 0) FROM orders"; var result = await cmd.ExecuteScalarAsync(ct); return Convert.ToInt64(result); }); var perShard = await Task.WhenAll(tasks); return perShard.Sum(); } } Program.cs using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using ShardingDemo; var builder = Host.CreateApplicationBuilder(args); // Bind Shards:[] from appsettings.json (override with user-secrets / env vars / Key Vault) var shardConfigs = builder.Configuration .GetSection("Shards") .Get<List<ShardConfig>>() ?? throw new InvalidOperationException("No 'Shards' section configured."); if (shardConfigs.Count == 0) throw new InvalidOperationException("At least one shard must be configured."); builder.Services.AddSingleton(_ => new ShardRouter(shardConfigs)); builder.Services.AddSingleton<UserRepository>(); using var host = builder.Build(); var repo = host.Services.GetRequiredService<UserRepository>(); var router = host.Services.GetRequiredService<ShardRouter>(); (long Id, string Email)[] users = { (1001, "ada@example.com"), (2002, "linus@example.com"), (3003, "grace@example.com"), (4004, "alan@example.com"), }; foreach (var (id, email) in users) { await repo.CreateUserAsync(id, email); Console.WriteLine($"user {id} -> shard {router.ShardForKey(id).Id}"); } await repo.AddOrderAsync(orderId: 9001, userId: 1001, amountCents: 4999); await repo.AddOrderAsync(orderId: 9002, userId: 1001, amountCents: 1299); await repo.AddOrderAsync(orderId: 9003, userId: 2002, amountCents: 8800); Console.WriteLine($"\nAda: {await repo.GetUserAsync(1001)}"); Console.WriteLine($"Ada's orders: {(await repo.GetOrdersForUserAsync(1001)).Count}"); Console.WriteLine($"\nTotal revenue across 3 shards: " + $"${await repo.TotalRevenueCentsAsync() / 100m:F2}"); await router.DisposeAsync(); Tracing One Request End to End Consider GetOrdersForUserAsync(1001): ShardForKey(1001) → MD5("1001") → first two bytes as a number → % 1024 → a bucket in the range 0..1023. bucket % 3 → a physical shard → for example mysql-shard-2.mysql.database.azure.com. The MySqlDataSource provides a pooled, TLS encrypted connection authenticated as shard2_admin. The query runs against shard 2's local ix_user index, with no fan out and at single server speed. Every call with userId = 1001, whether GetUser, AddOrder, or GetOrdersForUser, lands on the same shard. That is why orders JOIN users ON orders.user_id = users.user_id WHERE user_id = 1001 executes within a single shard, with no cross-shard traffic. Conclusion The essential point is this. Once a single primary can no longer absorb your write load, sharding becomes a durable answer, and implementing it at the application layer keeps every part of the system explicit and comprehensible. When write volume or dataset size outgrows a single primary, application layer sharding provides several benefits. N independent Azure Database for MySQL instances, each absorbing 1/N of the write traffic. Queries by user that remain on a single shard and behave like an ordinary, modestly sized database. A bucket map approach that allows you to add a fourth, fifth, or Nth shard later by relocating slices of data rather than rehashing the entire dataset. A failure of one shard that affects 1/N of your users rather than all of them. These benefits come at a genuine cost. You must generate identifiers in the application, global uniqueness requires a secondary lookup table, and aggregate queries fan out across shards. A cross shard write, one that must atomically update data on two different shards, can no longer rely on a single database transaction. Instead it needs an orchestrated sequence of local transactions, where each step carries a compensating action that undoes its effect if a later step fails. None of these are insurmountable. They are simply responsibilities you now assume. Sharding is a deliberate step to take only once a single primary has genuinely exhausted its write headroom. When you reach that point, the implementation in this post is a representative blueprint. Stay Connected We welcome your feedback and invite you to share your experiences or suggestions at AskAzureDBforMySQL@service.microsoft.com Thank you for choosing Azure Database for MySQL!133Views2likes0CommentsSolving Network Connectivity for MDE and MDI
Hi, I’m Will Sykes, a Senior Security Researcher in Microsoft IR. My colleagues and I specialize in helping customers with incident response and compromise recovery. In our work with customers who’ve been the victim of cyberattack, we often must solve connectivity issues to support tools in a hybrid cloud configuration. Hybrid Cloud Challenges In today’s IT world we’re now implementing and integrating services that leverage cloud computing components. Correctly securing this often defies the adage of “never allow internet connectivity to sensitive systems” and can pose challenges to security and systems administrators to find a solution. In this article we’re going to cover a potential solution to allow the communication traffic for Microsoft Defender for Endpoint (MDE) and Microsoft Defender for Identity (MDI) in a more secure manner, while simultaneously disallowing the operating system to reach the internet. MDE and MDI MDE and MDI are both cloud powered solutions that need to run on all assets (MDE) and Tier 0 assets (MDI). To function these products must be connected to Azure endpoints, however the number of endpoints (MDI, MDE) can be large. This poses a challenge to traditional firewalls that can’t do address-based filtering and rely on IP filtering. In addition, both services cannot function in an environment where SSL inspection is done on the traffic. Thankfully both solutions support a proxy server, and the greatest advantage here is that the proxy can be configured at the MDE/MDI application level and not at the operating system level. During an incident response (IR), Microsoft DART will often deploy MDE and MDI to support real time monitoring and automatic actions to evict threat actors. Depending on the current state of the organization it may not be possible to reconfigure existing firewalls or proxies to support the new configuration. Because of the rapid nature of an IR and the need to have clear consistent network communication data from our toolset we needed an “in a box” solution. The Proxy Solution Because it’s possible to configure MDE and MDI to use a proxy without relying on the system configuration we developed a preconfigured Squid proxy solution that can be deployed automatically via a shell script in a fully configured setup. Let’s look at the parts of this solution. Operating System and Software To use the proxy, you’ll need to create an Ubuntu Linux server. Any version greater than 18.04 will do, and this can be physical or virtual. You’ll need to size the machine based on your expected load; in our experience we’ve run tens of thousands of endpoints through a proxy with 4 CPU cores and 16 GB of memory. The automation script will enable the Uncomplicated Firewall (UFW) and allow ports 22 for SSH and 3128 for Squid. The Squid install is basic; the configuration and the tuning is done via the script. The deploy script will build an endpoint file at /etc/squid/mdemdi.conf which contains the list of required cloud endpoints for MDE and MDI. It then creates a squid configuration file at /etc/squid/squid.conf with some defaults and lockdowns: The hosts allowed to use the firewall are the RFC 1918 internal networks. This can be scoped down or reconfigured depending on your network needs. The only destinations allowed by the proxy are in the mdemdi.conf file, every other destination is denied and only HTTP/HTTPS is allowed. Lastly the script will add some operating system tuning to support deployments in large environments based on our IR experiences. Internal Network Configuration Once you have the proxy configured, you’ll need to ensure that it can connect outbound to the internet unfiltered. If you have SSL filtering enabled on the proxy this will still cause MDE and MDI to fail communications. The proxy is configured to deny all internet destinations not needed by MDE and MDI, so this is a compensating control to avoid having unfiltered internet access. Of course, you can also add the identical filters to the network security devices allowing the proxy outbound internet. The next configuration step is to allow your internal networks to communicate to the proxy on TCP 3128, which is the configured listener for Squid. Configuring MDE and MDI MDE and MDI Version 3 To configure MDE you can use Group Policy to either use the native template or to manage the underlying Windows registry values. The setting can be found at Administrative Templates > Windows Components > Microsoft Defender Antivirus > Define proxy server for connecting to the network. And at Administrative Templates > Windows Components > Data Collection and Preview Builds > Configure connected user experiences and telemetry If you need to configure devices not managed by Group Policy you can manage the following registry values: HKLM\Software\Policies\Microsoft\Windows Defender\ProxyServer This is a registry value of type REG_SZ and takes the following string format: http://<server name or IP>:<port> HKLM\Software\Policies\Microsoft\Windows\DataCollection\TelemetryProxyServer This is a registry value of type REG_SZ and takes the following string format: servername:port or ip:port That’s it! Once that’s done you can confirm connectivity in the Windows Event View in the Applications and Services Logs -> Microsoft -> Windows -> Windows Defender -> Operational log. MDI Version 2 MDI can be installed directly with the proxy, however this must be done on the command line and not through the GUI. To do this add the ProxyUrl=" http://<server name or IP>:<port>" option the installer parameters. If you’ve already installed MDI and need to reconfigure the proxy that’s perfectly fine! You can use the handy MDI PowerShell Module's Set-MDISensorProxyConfiguration cmdlet or the built in utility Microsoft.Tri.Sensor.Deployment.Deployer.exe. Conclusion Based on our experiences in incident response, connectivity issues can slow or block vital security tools. The proxy solution allows network administrators to utilize a pre-configured appliance type solution to support MDE and MDI without having to modify existing security rulesets. A systems administrator can simplify their deployment of MDE or MDI by including the proxy solution as part of the application bundle, where its lifecycle can be tracked in lockstep with the application. Now the moment you’ve all been waiting for! The script can be downloaded at https://github.com/mswillsykes/squidmdemdi. Disclaimer The sample scripts are not supported under any Microsoft standard support program or service. The sample scripts are provided AS IS without warranty of any kind. Microsoft further disclaims all implied warranties including, without limitation, any implied warranties of merchantability or of fitness for a particular purpose. The entire risk arising out of the use or performance of the sample scripts and documentation remains with you. In no event shall Microsoft, its authors, or anyone else involved in the creation, production, or delivery of the scripts be liable for any damages whatsoever (including, without limitation, damages for loss of business profits, business interruption, loss of business information, or other pecuniary loss) arising out of the use of or inability to use the sample scripts or documentation, even if Microsoft has been advised of the possibility of such damages.Lesson Learned #519: Reusing Connections in Azure SQL DB: How Connection Pooling Caches Your Session
A few days ago, I was working on a case where a customer reported an unexpected behavior in their application: even after switching the connection policy from Proxy to Redirect, the connections were still using Proxy mode. After investigating, we found that the customer was using connection pooling, which caches connections for reuse. This meant that even after changing the connection policy, the existing connections continued using Proxy mode because they had already been established with that setting. The new policy would only apply to newly created connections, not the ones being reused from the pool. To confirm this, we ran a test using .NET and Microsoft.Data.SqlClient to analyze how the connection pool behaves and whether connections actually switch to Redirect mode when the policy changes. How Connection Pooling Works Connection pooling is designed to reuse existing database connections instead of creating a new one for every request. This improves performance by reducing latency and avoiding unnecessary authentication handshakes. However, once a connection is established, it is cached with the original settings, including: Connection policy (Proxy or Redirect) Authentication mode Connection encryption settings This means that if you change the connection policy but reuse a pooled connection, it will retain its original mode. The only way to apply the new policy is to create a new physical connection that does not come from the pool. Testing Connection Pooling Behavior For Testing the connection pooling behavior, I developed this small code in C# that basically, opens the connection, provides information about the port using and close the connection. Repeating this process 10000 times. The idea was to track active connections and check if the port and connection policy were changing after modifying the connection policy. Initially, I attemped to use netstat -ano to track active connections and monitor the local port used by each session. Unfortunately, in Azure SQL Database, local port information is not reported, making it difficult to confirm whether a connection was truly being reused at the OS level. Despite this limitation, by analyzing the session behavior and connection reuse patterns, we were able to reach a clear conclusion. using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading; using System.Threading.Tasks; using Microsoft.Data.SqlClient; namespace InfoConn { using System; using System.Data; using System.Diagnostics; using System.Text.RegularExpressions; using System.Threading; using Microsoft.Data.SqlClient; class Program { static void Main() { string connectionStringProxy = "Server=tcp:servername.database.windows.net,1433;Database=db1;User Id=user1;Password=..;Pooling=True;"; Console.WriteLine("Starting Connection Pooling Test"); for (int i = 0; i < 10000; i++) { using (SqlConnection conn = new SqlConnection(connectionStringProxy)) { conn.Open(); ShowConnectionDetails(conn, i); } Thread.Sleep(5000); } Console.WriteLine("Test complete."); } static void ShowConnectionDetails(SqlConnection conn, int attempt) { string query = "SELECT session_id, client_net_address, local_net_address, auth_scheme FROM sys.dm_exec_connections WHERE session_id = @@SPID;"; using (SqlCommand cmd = new SqlCommand(query, conn)) { using (SqlDataReader reader = cmd.ExecuteReader()) { while (reader.Read()) { Console.WriteLine($"[Attempt {attempt + 1}] Session ID: {reader["session_id"]}"); Console.WriteLine($"[Attempt {attempt + 1}] Client IP: {reader["client_net_address"]}"); Console.WriteLine($"[Attempt {attempt + 1}] Local IP: {reader["local_net_address"]}"); Console.WriteLine($"[Attempt {attempt + 1}] Auth Scheme: {reader["auth_scheme"]}"); } } } RetrievePortInformation(attempt); } static void RetrievePortInformation(int attempt) { try { int currentProcessId = Process.GetCurrentProcess().Id; Console.WriteLine($"[Attempt {attempt + 1}] PID: {currentProcessId}"); string netstatOutput = RunNetstatCommand(); var match = Regex.Match(netstatOutput, $@"\s*TCP\s*(\S+):(\d+)\s*(\S+):(\d+)\s*ESTABLISHED\s*{currentProcessId}"); if (match.Success) { string localAddress = match.Groups[1].Value; string localPort = match.Groups[2].Value; string remoteAddress = match.Groups[3].Value; string remotePort = match.Groups[4].Value; Console.WriteLine($"[Attempt {attempt + 1}] Local IP: {localAddress}"); Console.WriteLine($"[Attempt {attempt + 1}] Local Port: {localPort}"); Console.WriteLine($"[Attempt {attempt + 1}] Remote IP: {remoteAddress}"); Console.WriteLine($"[Attempt {attempt + 1}] Remote Port: {remotePort}"); } else { Console.WriteLine($"[Attempt {attempt + 1}] No active TCP connection found in netstat."); } } catch (Exception ex) { Console.WriteLine($"[Attempt {attempt + 1}] Error retrieving port info: {ex.Message}"); } } static string RunNetstatCommand() { using (Process netstatProcess = new Process()) { netstatProcess.StartInfo.FileName = "netstat"; netstatProcess.StartInfo.Arguments = "-ano"; netstatProcess.StartInfo.RedirectStandardOutput = true; netstatProcess.StartInfo.UseShellExecute = false; netstatProcess.StartInfo.CreateNoWindow = true; netstatProcess.Start(); string output = netstatProcess.StandardOutput.ReadToEnd(); netstatProcess.WaitForExit(); return output; } } } }715Views0likes0CommentsWhen using proxy on localhost teams fails to establish TCP connection for login
This is a strange problem, and it took us quite a while to get to the bottom of it. We tested on Windows 10 and Windows 11 with the latest Teams app. What is the problem? We are working on a solution the monitors network traffic for a safe school environment. As part of that we install a local proxy on Windows devices. The proxy is configured as manual proxy in the proxy settings. When requesting to sign into Teams, Teams establishes a connection to `login.microsoftonline.com`. Without the proxy that works without problems. Teams establishes a TCP connection, sends a CONNECT, TLS handshake and then encrypted data. When activating the local proxy any connection is fine (e.g. `teams.events.data.microsoft.com`, `nav.smartscreen.microsoft.com` are established just fine), but the one to `login.microsoftonline.com` is not established. In Teams this results in an error page that the login page can't be reached (it reports a 404, but it's not a 404). We ensured that nothing was blocked and there are no lower level connection errors, then we dug deeper with Wireshark. Once accessing the login page (which would trigger connection making for `login.microsoftonline.com`) we see a `SYN` being sent to establish the TCP connection, but there is never a `SYN/ACK`. The local proxy never receives this `SYN` (no connection is ever established), somehow it never reaches the destination but is "dropped". We can see that Teams tries to re-transmit the SYN, but it never arrives at the destination. A tcp `SYN` not reaching it's destination points to it being blocked somewhere, so we: - Turned the firewall off (anything that can be turned off is turned off) - Added specific allow rules for any other device control that could block. This did not help, the problem keeps persisting. (Note: We were able to reproduce this problem with a different proxy solution as well. Once we configure the proxy on localhost we run into this issue.) We also tried: - Configure the local proxy with the IP address of the machine within the local network (i.e. 10.12.128.2) instead of `127.0.0.1`: Leads to the same problem. - Run the local proxy on another Windows machine, turn off firewall and connect to the proxy on the other machine: No problems with this solution, the connection is established fine. We then tried to configure the proxy via PAC file (without a DIRECT fallback), which result in different weird behavior. In the browser the behavior is as expected - pages that are blocked by the proxy report a connection failure, pages that are allowed can be accessed. For Teams login we don't run into problems with the PAC file based settings, but Teams does not route the connection to `login.microsoftonline.com` through the proxy! The connection to `login.microsoftline.com` somehow implicitly bypasses the system wide proxy settings when using a PAC file (i.e. I see the connection in Wireshark, but it's directly established, not going through the proxy even though it is configured via PAC file). Other connections related to Teams are established through the proxy as expected. I don't think this is expected behavior. I have Wireshark traces that show all the different behaviors. If needed I'm happy to add them to this ticket. It would be great to get help with a better understanding what could be the problem. What could cause this behavior (in Teams / in Windows)? With a better understanding of the behavior we could work on overcoming this problem.2KViews0likes4CommentsAdding Custom Header to Microsoft Teams Login Requests Using mitmproxy Causes HTTP 404 Error
Hi Team, We are using mitmproxy to add a custom header to all requests during the login process in the Microsoft Teams Windows application. The purpose of adding this custom header is to verify that the request is coming from a specific device by checking this header on our sso server. Unfortunately, we are unable to bypass the login URLs. Adding the custom header is a crucial part of our verification process. However, we are encountering the following issue: When using mitmproxy to modify requests, the Microsoft Teams app fails to connect and shows the error: "We can't connect you. HTTP 404 login.microsoftonline.com" Is there a way to add a custom header to requests from the Teams app without breaking the connection to the Microsoft login endpoint? We appreciate any guidance on how to resolve this issue or insights into how Teams validates its login requests to avoid potential interference. Looking forward to your reply. Thanks227Views0likes0CommentsDefender for Endpoint and disconnected environments. Which proxy configuration wins?
This article is a follow-up to a previous one discussing conflicting proxy configurations and how Microsoft Defender for Endpoint behaves in these situations. The first article can be found in here. In this article we'll explore how Defender for Endpoint network traffic flows depending on which proxy configuration is in use, as well as what network traffic looks like when all three proxy configurations are set.Lesson Learned #431: Determining Connection Type to Azure SQL Database: Proxy or Redirect
When connecting to Azure SQL Database, it's important to understand the type of connection established. Azure offers two primary connection policies: Proxy and Redirect. Knowing which one you're using can be crucial for performance considerations and troubleshooting.5.3KViews0likes0CommentsClearing WPAD cache and settings (solution)
Hi all, I want to share the solution to clear all WPAD cache and settings on a Windows 10 or 11 machine. Step 1: Regedit Computer\HKEY_USERS\S-1-5-19\SOFTWARE\Microsoft\Windows\CurrentVersion\Internet Settings\Wpad: delete all underlying keys Computer\HKEY_CURRENT_USER\SOFTWARE\Microsoft\Windows\CurrentVersion\Internet Settings\Connections: delete the keys "DefaultConnectionSettings" and "SaveLegacySettings" Computer\HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\WinHttpAutoProxySvc: in the value "start", change the data value from "3" to "4 Step 2: Internet Options Advanced tab > Reset Internet Explorer Settings (incl Delete personal settings) > Reset Step 3: CMD as admin ipconfig /flushdns Step 4: reboot the machine twice Clearing the WPAD can be useful when getting this output from dsregcmd /status "Auto Detect PAC Status : Failed to auto detect the Proxy Auto-Configuration (PAC) script using WPAD. code: 0x80072f94" (Hybrid Azure AD Join Failure)21KViews0likes0Comments