Вы находитесь на странице: 1из 36

Networking Programming Topics

Contents

Introduction 4
How to Use This Document 4 Prerequisites 5

Using Sockets and Socket Streams 6


Choosing an API Family 7 Writing a TCP-Based Client 8 Establishing a Connection 9 Handling Events 10 Closing the Connection 10 For More Information 11 Writing a TCP-Based Server 11 Listening with Core Foundation 11 Listening with POSIX Socket APIs 15 Working with Packet-Based Sockets 18 Obtaining the Native Socket Handle for a Socket Stream 20

Resolving DNS Hostnames 22


Resolving Hostnames with CFHost 22 Resolving Hostnames with POSIX Calls 23

Overriding TLS Chain Validation Correctly 25


Manipulating Trust Objects 26 Trust Objects and NSURLConnection 28 Trust Objects and NSStream 31

Document Revision History 35

2012-12-13 | 2012 Apple Inc. All Rights Reserved.

Listings

Overriding TLS Chain Validation Correctly 25


Listing 1 Listing 2 Listing 3 Listing 4 Listing 5 Adding an anchor to a SecTrustRef object 26 Changing the remote hostname for a SecTrustRef object 27 Overriding the trust object used by an NSURLConnection object 29 Overriding the TLS hostname with NSStream 31 Using custom TLS anchors with NSStream 32

2012-12-13 | 2012 Apple Inc. All Rights Reserved.

Introduction

Important: This is a preliminary document. Although it has been reviewed for technical accuracy, it is not final. Apple is supplying this information to help you adopt the technologies and programming interfaces described herein. This information is subject to change, and software implemented according to this document should be vetted against final documentation. For information about updates to this document, go to the Apple Developer website. In the relevant reference library, enter the document's title in the Documents text field that appears. This document is a collection of highly specialized, task-based articles related to specific areas of networking. As with other programming topics documents in this developer library, this document assumes that you already have a deep familiarity with networking concepts. Important: Most developers do not need to read this document, and most networking software does not need to do any of the things described in this document. Before you read this document, you should read Networking Overview to learn more about when you should perform the tasks described in these articles.

How to Use This Document


This document includes the following articles:

Using Sockets and Socket Streams (page 6)Describes how to use sockets and streams for low-level networking, from the POSIX layer up through the Foundation layer. This article explains how to write both client and server code using current best practices. Resolving DNS Hostnames (page 22)Explains how to resolve DNS hostnames in ways that avoid some of the common pitfalls associated with doing so. Overriding TLS Chain Validation Correctly (page 25)Tells how to safely alter the behavior of Transport Layer Security (TLS) chain validation without exposing your software to serious security risks. This article covers both TCP streams (using the CFStream or NSStream API) and URL requests (using the NSURLConnection API).

Each article is intended to be read by developers who need to write code that performs the specified task.

2012-12-13 | 2012 Apple Inc. All Rights Reserved.

Introduction Prerequisites

Prerequisites
This document assumes you have already read or otherwise understand the subjects described in the following documents:

Networking Overview Provides a basic understanding of how networking software works, and how to avoid common mistakes. Networking Concepts Provides a basic explanation of socket-based networking at a conceptual level.

2012-12-13 | 2012 Apple Inc. All Rights Reserved.

Using Sockets and Socket Streams

This article explains how to work with sockets and socket streams at various levels, from POSIX through Foundation. Important: This article describes ways to make socket connections that are completely under the control of your program. Most programs would be better served by higher-level APIs such as NSURLConnection. To learn more about these higher-level APIs, read Networking Overview . The APIs described in this article should be used only if you need to support some protocol other than the protocols supported by built-in Cocoa or Core Foundation functionality. At almost every level of networking, software can be divided into two categories: clients (programs that connect to other apps) and services (programs that other apps connect to). At a high level, these lines are clear. Most programs written using high-level APIs are purely clients. At a lower level, however, the lines are often blurry. Socket and stream programming generally falls into one of the following broad categories:

Packet-based communicationPrograms that operate on one packet at a time, listening for incoming packets, then sending packets in reply. With packet-based communication, the only differences between clients and servers are the contents of the packets that each program sends and receives, and (presumably) what each program does with the data. The networking code itself is identical.

Stream-based clientsPrograms that use TCP to send and receive data as two continuous streams of bytes, one in each direction. With stream-based communication, clients and servers are somewhat more distinct. The actual data handling part of clients and servers is similar, but the way that the program initially constructs the communication channel is very different.

This chapter is divided into sections based on the above tasks:

Choosing an API Family (page 7)Describes how to decide which API family to use when writing networking code. Writing a TCP-Based Client (page 8)Describes how to make outgoing TCP connections to existing servers and services.

2012-12-13 | 2012 Apple Inc. All Rights Reserved.

Using Sockets and Socket Streams Choosing an API Family

Writing a TCP-Based Server (page 11)Describes how to listen for incoming TCP connections when writing servers and services. Working with Packet-Based Sockets (page 18)Describes how to work with non-TCP protocols, such as UDP.

Choosing an API Family


The API you choose for socket-based connections depends on whether you are making a connection to another host or receiving a connection from another host. It also depends on whether you are using TCP or some other protocol. Here are a few factors to consider:

In OS X, if you already have networking code that is shared with non-Apple platforms, you can use POSIX C networking APIs and continue to use your networking code as-is (on a separate thread). If your program is based on a Core Foundation or Cocoa (Foundation) run loop, you can also use the Core Foundation CFStream API to integrate the POSIX networking code into your overall architecture on the main thread. Alternatively, if you are using Grand Central Dispatch (GCD), you can add a socket as a dispatch source. In iOS, POSIX networking is discouraged because it does not activate the cellular radio or on-demand VPN. Thus, as a general rule, you should separate the networking code from any common data processing functionality and rewrite the networking code using higher-level APIs. Note: If you use POSIX networking code, you should be aware that the POSIX networking API is not protocol-agnostic (you must handle some of the differences between IPv4 and IPv6 yourself ). It is a connect-by-IP API rather than a connect-by-name API, which means that you must do a lot of extra work if you want to achieve the same initial-connection performance and robustness that higher-level APIs give you for free. Before you decide to reuse existing POSIX networking code, be sure to read Avoid Resolving DNS Names Before Connecting to a Host in Networking Overview .

For daemons and services that listen on a port, or for non-TCP connections, use POSIX or Core Foundation (CFSocket) C networking APIs. For client code in Objective-C, use Foundation Objective-C networking APIs. Foundation defines high-level classes for managing URL connections, socket streams, network services, and other networking tasks. It is also the primary non-UI Objective-C framework in OS X and iOS, providing routines for run loops, string handling, collection objects, file access, and so on. For client code in C, use Core Foundation C networking APIs. The Core Foundation framework and the CFNetwork framework are two of the primary C-language frameworks in OS X and iOS. Together they define the functions and structures upon which the Foundation networking classes are built.

2012-12-13 | 2012 Apple Inc. All Rights Reserved.

Using Sockets and Socket Streams Writing a TCP-Based Client

Note: In OS X, CFNetwork is a subframework of the Core Services framework; in iOS, CFNetwork is a top-level framework.

Writing a TCP-Based Client


The way you make an outgoing connection depends on what programming language you are using, on the type of connection (TCP, UDP, and so forth), and on whether you are trying to share code with other (non-Mac, non-iOS) platforms.

Use NSStream for outgoing connections in Objective-C. If you are connecting to a specific host, create a CFHost object (not NSHostthey are not toll-free bridged), then use CFStreamCreatePairWithSocketToHost or CFStreamCreatePairWithSocketToCFHost to open a socket connected to that host and port and associate a pair of CFStream objects with it. You can then cast these to an NSStream object. You can also use the CFStreamCreatePairWithSocketToNetService function with a CFNetServiceRef object to connect to a Bonjour service. Read Discovering and Advertising Network Services in Networking Overview for more information. Note: The getStreamsToHost:port:inputStream:outputStream: of NSNetService is nod available on iOS, and is discouraged on OS X for performance reasons. Specifically, NSNetService requires you to create an instance of NSHost. When you create the object, the lookup is performed synchronously. Thus, it is unsafe to construct an NSHost object on your main application thread. See NSNetService and Automatic Reference Counting (ARC) for details.

Use CFStream for outgoing connections in C. If you are writing code that cannot include Objective-C, use the CFStream API. It integrates more easily with other Core Foundation APIs than CFSocket, and enables the cellular hardware on iOS (where applicable), unlike lower-level APIs. You can use CFStreamCreatePairWithSocketToHost or CFStreamCreatePairWithSocketToCFHost to open a socket connected to a given host and port and associate a pair of CFStream objects with it. You can also use the CFStreamCreatePairWithSocketToNetService function to connect to a Bonjour service. Read Discovering and Advertising Network Services in Networking Overview for more information.

Use POSIX calls if cross-platform portability is required.

2012-12-13 | 2012 Apple Inc. All Rights Reserved.

Using Sockets and Socket Streams Writing a TCP-Based Client

If you are writing networking code that runs exclusively in OS X and iOS, you should generally avoid POSIX networking calls, because they are harder to work with than higher-level APIs. However, if you are writing networking code that must be shared with other platforms, you can use the POSIX networking APIs so that you can use the same code everywhere. Never use synchronous POSIX networking APIs on the main thread of a GUI application. If you use synchronous networking calls in a GUI application, you must do so on a separate thread. Note: POSIX networking does not activate the cellular radio on iOS. For this reason, the POSIX networking API is generally discouraged in iOS.

The subsections below describe the use of NSStream. Except where noted, the CFStream API has functions with similar names, and behaves similarly. To learn more about the POSIX socket API, read the UNIX Socket FAQ at http://developerweb.net/.

Establishing a Connection
As a rule, the recommended way to establish a TCP connection to a remote host is with streams. Streams automatically handle many of the challenges that TCP connections present. For example, streams provide the ability to connect by hostname, and in iOS, they automatically activate a devices cellular modem or on-demand VPN when needed (unlike CFSocket or BSD sockets). Streams are also a more Cocoa-like networking interface than lower-level protocols, behaving in a way that is largely compatible with the Cocoa file stream APIs. The way you obtain input and output streams for a host depends on whether you used service discovery to discover the host:

If you already know the DNS name or IP address of the remote host, obtain Core Foundation read (input) and write (output) streams with the CFStreamCreatePairWithSocketToHost function. You can then take advantage of the toll-free bridge between CFStream and NSStream to cast your CFReadStreamRef and CFWriteStreamRef objects to NSInputStream and NSOutputStream objects. If you discovered the host by browsing for network services with a CFNetServiceBrowser object, you obtain input and output streams for the service with the CFStreamCreatePairWithSocketToNetService function. Read Discovering and Advertising Network Services in Networking Overview for more information.

After you have obtained your input and output streams, you should retain them immediately if you are not using automatic reference counting. Then cast them to NSInputStream and NSOutputStream objects, set their delegate objects (which should conform to the NSStreamDelegate protocol), schedule them on the current run loop, and call their open methods.

2012-12-13 | 2012 Apple Inc. All Rights Reserved.

Using Sockets and Socket Streams Writing a TCP-Based Client

Note: If you are working with more than one connection at a time, you must also keep track of which input stream is associated with a given output stream and vice versa. The most straightforward way to do this is to create your own connection object that holds references to both streams, and then set that object as the delegate for each stream.

Handling Events
When the stream:handleEvent: method is called on the NSOutputStream objects delegate and the streamEvent parameters value is NSStreamEventHasSpaceAvailable, call write:maxLength: to send data. This method returns the number of bytes written or a negative number on error. If fewer bytes were written than you tried to send, you must queue up the remaining data and send it after the delegate method gets called again with an NSStreamEventHasSpaceAvailable event. If an error occurs, you should call streamError to find out what went wrong. When the stream:handleEvent: method is called on your NSInputStream objects delegate and the streamEvent parameters value is NSStreamEventHasBytesAvailable, your input stream has received data that you can read with the read:maxLength: method. This method returns the number of bytes read, or a negative number on error. If fewer bytes were read than you need, you must queue the data and wait until you receive another stream event with additional data. If an error occurs, you should call streamError to find out what went wrong. If the other end of the connection closes the connection:

Your connection delegates stream:handleEvent: method is called with streamEvent set to NSStreamEventHasBytesAvailable. When you read from that stream, you get a length of zero (0). Your connection delegates stream:handleEvent: method is called with streamEvent set to NSStreamEventEndEncountered.

When either of these two events occurs, the delegate method is responsible for detecting the end-of-file condition and cleaning up.

Closing the Connection


To close your connection, unschedule it from the run loop, set the connections delegate to nil (the delegate is unretained), close both of the associated streams with the close method, and then release the streams themselves (if you are not using ARC) or set them to nil (if you are). By default, this closes the underlying socket connection. There are two situations in which you must close it yourself, however:

If you previously set the kCFStreamPropertyShouldCloseNativeSocket to kCFBooleanFalse by calling setProperty:forKey: on the stream.

2012-12-13 | 2012 Apple Inc. All Rights Reserved.

10

Using Sockets and Socket Streams Writing a TCP-Based Server

If you created the streams based on an existing BSD socket by calling CFStreamCreatePairWithSocket. By default, streams created from an existing native socket do not close their underlying socket. However, you can enable automatic closing by setting the kCFStreamPropertyShouldCloseNativeSocket to kCFBooleanTrue with the setProperty:forKey: method.

For More Information


To learn more, read Setting Up Socket Streams in Stream Programming Guide , Using NSStreams For A TCP Connection Without NSHost , or see the SimpleNetworkStreams and RemoteCurrency sample code projects.

Writing a TCP-Based Server


As mentioned previously, a server and a client are similar once the connection is established. The main difference is that clients make outgoing connections, whereas servers create a listening socket (sometimes listen socket)a socket that listens for incoming connectionsthen accept connections on that socket. After that, each resulting connection behaves just like a connection you might make in a client. The API you should choose for your server depends primarily on whether you are trying to share the code with other (non-Mac, non-iOS) platforms. There are only two APIs that provide the ability to listen for incoming network connections: the Core Foundation socket API and the POSIX (BSD) socket API. Higher-level APIs cannot be used for accepting incoming connections.

If you are writing code for OS X and iOS exclusively, use POSIX networking calls to set up your network sockets. Then, use GCD or CFSocket to integrate the sockets into your run loop. Use pure POSIX networking code with a POSIX-based run loop (select) if cross-platform portability with non-Apple platforms is required. If you are writing networking code that runs exclusively in OS X and iOS, you should generally avoid POSIX networking calls because they are harder to work with than higher level APIs. However, if you are writing networking code that must be shared with other platforms, you can use the POSIX networking APIs so that you can use the same code everywhere.

Never use NSSocketPort or NSFileHandle for general socket communication. For details, see Do Not Use NSSocketPort (OS X) or NSFileHandle for General Socket Communication in Networking Overview .

The following sections describe how to use these APIs to listen for incoming connections.

Listening with Core Foundation


To use Core Foundation APIs to listen for incoming connections, you must do the following:

2012-12-13 | 2012 Apple Inc. All Rights Reserved.

11

Using Sockets and Socket Streams Writing a TCP-Based Server

1.

Add appropriate includes:


#include <CoreFoundation/CoreFoundation.h> #include <sys/socket.h> #include <netinet/in.h>

2.

Create socket objects (returned as a CFSocketRef object) with the CFSocketCreate or CFSocketCreateWithNative function. Specify kCFSocketAcceptCallBack as the callBackTypes parameter value. Provide a pointer to a CFSocketCallBack callback function as the callout parameter value.
CFSocketRef myipv4cfsock = CFSocketCreate( kCFAllocatorDefault, PF_INET, SOCK_STREAM, IPPROTO_TCP, kCFSocketAcceptCallBack, handleConnect, NULL); CFSocketRef myipv6cfsock = CFSocketCreate( kCFAllocatorDefault, PF_INET6, SOCK_STREAM, IPPROTO_TCP, kCFSocketAcceptCallBack, handleConnect, NULL);

3.

Bind a socket with the CFSocketSetAddress function. Provide a CFData object containing a sockaddr struct that specifies information about the desired port and family.
struct sockaddr_in sin;

memset(&sin, 0, sizeof(sin)); sin.sin_len = sizeof(sin); sin.sin_family = AF_INET; /* Address family */ sin.sin_port = htons(0); /* Or a specific port */ sin.sin_addr.s_addr= INADDR_ANY;

2012-12-13 | 2012 Apple Inc. All Rights Reserved.

12

Using Sockets and Socket Streams Writing a TCP-Based Server

CFDataRef sincfd = CFDataCreate( kCFAllocatorDefault, (UInt8 *)&sin, sizeof(sin));

CFSocketSetAddress(myipv4cfsock, sincfd); CFRelease(sincfd);

struct sockaddr_in6 sin6;

memset(&sin6, 0, sizeof(sin6)); sin6.sin6_len = sizeof(sin6); sin6.sin6_family = AF_INET6; /* Address family */ sin6.sin6_port = htons(0); /* Or a specific port */ sin6.sin6_addr = in6addr_any;

CFDataRef sin6cfd = CFDataCreate( kCFAllocatorDefault, (UInt8 *)&sin6, sizeof(sin6));

CFSocketSetAddress(myipv6cfsock, sin6cfd); CFRelease(sin6cfd);

4.

Begin listening on a socket by adding the socket to a run loop. Create a run-loop source for a socket with the CFSocketCreateRunLoopSource function. Then, add the socket to a run loop by providing its run-loop source to the CFRunLoopAddSource function.
CFRunLoopSourceRef socketsource = CFSocketCreateRunLoopSource( kCFAllocatorDefault, myipv4cfsock, 0);

CFRunLoopAddSource( CFRunLoopGetCurrent(),

2012-12-13 | 2012 Apple Inc. All Rights Reserved.

13

Using Sockets and Socket Streams Writing a TCP-Based Server

socketsource, kCFRunLoopDefaultMode);

CFRunLoopSourceRef socketsource6 = CFSocketCreateRunLoopSource( kCFAllocatorDefault, myipv6cfsock, 0);

CFRunLoopAddSource( CFRunLoopGetCurrent(), socketsource6, kCFRunLoopDefaultMode);

After this, you can access the underlying BSD socket descriptor with the CFSocketGetNative function. When you are through with the socket, you must close it by calling CFSocketInvalidate. In your listening sockets callback function (handleConnect in this case), you should check to make sure the value of the callbackType parameter is kCFSocketAcceptCallBack, which means that a new connection has been accepted. In this case, the data parameter of the callback is a pointer to a CFSocketNativeHandle value (an integer socket number) representing the socket. To handle the new incoming connections, you can use the CFStream, NSStream, or CFSocket APIs. The stream-based APIs are strongly recommended. To do this:
1. 2.

Create read and write streams for the socket with the CFStreamCreatePairWithSocket function. Cast the streams to an NSInputStream object and an NSOutputStream object if you are working in Cocoa. Use the streams as described in Writing a TCP-Based Client (page 8).

3.

For more information, see CFSocket Reference . For sample code, see the RemoteCurrency and WiTap sample code projects.

2012-12-13 | 2012 Apple Inc. All Rights Reserved.

14

Using Sockets and Socket Streams Writing a TCP-Based Server

Listening with POSIX Socket APIs


POSIX networking is fairly similar to the CFSocket API, except that you have to write your own run-loop-handling code. Important: Never use POSIX networking APIs on the main thread of a GUI application. If you use POSIX networking in a GUI application, you must either do so on a separate thread or use GCD. Here are the basic steps for creating a POSIX-level server:
1.

Create a socket by calling socket. For example:


int ipv4_socket = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP); int ipv6_socket = socket(PF_INET6, SOCK_STREAM, IPPROTO_TCP);

2.

Bind it to a port.

If you have a specific port in mind, use that. If you dont have a specific port in mind, pass zero for the port number, and the operating system will assign you an ephemeral port. (If you are going to advertise your service with Bonjour, you should almost always use an ephemeral port.)

For example:
struct sockaddr_in sin; memset(&sin, 0, sizeof(sin)); sin.sin_len = sizeof(sin); sin.sin_family = AF_INET; // or AF_INET6 (address family) sin.sin_port = htons(0); sin.sin_addr.s_addr= INADDR_ANY;

if (bind(listen_sock, (struct sockaddr *)&sin, sizeof(sin)) < 0) { // Handle the error. }

3.

If you are using an ephemeral port, call getsockname to find out what port you are using. You can then register this port with Bonjour. For example:
socklen_t len = sizeof(sin);

2012-12-13 | 2012 Apple Inc. All Rights Reserved.

15

Using Sockets and Socket Streams Writing a TCP-Based Server

if (getsockname(listen_sock, (struct sockaddr *)&sin, &len) < 0) { // Handle error here } // You can now get the port number with ntohs(sin.sin_port).

4.

Call listen to begin listening for incoming connections on that port.

The next steps depend on whether you intend to use pure POSIX socket code or a higher level abstraction.

Handling Events with Core Foundation


Call CFSocketCreateWithNative. Then follow the directions in Listening with Core Foundation (page 11), beginning at step 3.

Handling Events with Grand Central Dispatch


GCD allows you to perform operations asynchronously, and provides an event queue mechanism for determining when to read data from the socket. After creating the listening socket, a GCD-based server should:
1.

Call dispatch_source_create to create a dispatch source for the listening socket, specifying DISPATCH_SOURCE_TYPE_READ as the source type. Call dispatch_source_set_event_handler (or dispatch_source_set_event_handler_f and dispatch_set_context) to set a handler that gets called whenever a new connection arrives on the socket. When the listen socket handler is called (upon a new connection), it should:

2.

3.

Call accept. This function fills a new sockaddr structure with information about the connection and returns a new socket for that connection. If desired, call ntohl(my_sockaddr_obj.sin_addr.s_addr) to determine the clients IP address.

Call dispatch_source_create to create a dispatch source for the client socket, specifying DISPATCH_SOURCE_TYPE_READ as the source type. Call setsockopt to set the SO_NOSIGPIPE flag on the socket. Call dispatch_source_set_event_handler (or dispatch_source_set_event_handler_f and dispatch_set_context) to set a handler that gets called whenever the state of the connection changes.

4.

In the client socket handler, call dispatch_async or dispatch_async_f and pass a block that calls read on the socket to grab any new data, then handle that data appropriately. This block can also send responses by calling write on the socket.

2012-12-13 | 2012 Apple Inc. All Rights Reserved.

16

Using Sockets and Socket Streams Writing a TCP-Based Server

Handling Events with Pure POSIX Code


1.

Create a file descriptor set and add new sockets to that set as new connections come in.
fd_set incoming_connections; memset(&incoming_connections, 0, sizeof(incoming_connections));

2.

If you need to perform actions periodically on your networking thread, construct a timeval structure for the select timeout.
struct timeval tv; tv.tv_sec = 1; /* 1 second timeout */ tv.tv_usec = 0; /* no microseconds. */

It is important to choose a timeout that is reasonable. Short timeout values bog down the system by causing your process to run more frequently than is necessary. Unless you are doing something very unusual, your select loop should not wake more than a few times per second, at most, and on iOS, you should try to avoid doing this at all. For alternatives, read Common Networking Mistakes in Networking Overview . If you do not need to perform periodic actions, pass NULL.
3.

Call select in a loop, passing two separate copies of that file descriptor set (created by calling FD_COPY) for the read and write descriptor sets. The select system call modifies these descriptor sets, clearing any descriptors that are not ready for reading or writing. For the timeout parameter, pass the timeval structure you created earlier. Although OS X and iOS do not modify this structure, some other operating systems replace this value with the amount of time remaining. Thus, for cross-platform compatibility, you must reset this value each time you call select. For the nfds parameter, pass a number that is one higher than the highest-numbered file descriptor that is actually in use.

4.

Read data from sockets, calling FD_ISSET to determine if a given socket has pending data. Write data to calling FD_ISSET to determine if a given socket has room for new data. Maintain appropriate queues for incoming and outgoing data.

As an alternative to the POSIX select function, the BSD-specific kqueue API can also be used to handle socket events.

For More Information


To learn more about POSIX networking, read the socket, listen, FD_SET, and select manual pages.

2012-12-13 | 2012 Apple Inc. All Rights Reserved.

17

Using Sockets and Socket Streams Working with Packet-Based Sockets

Working with Packet-Based Sockets


The recommended way to send and receive UDP packets is by combining the POSIX API and either the CFSocket or GCD APIs. To use these APIs, you must perform the following steps:
1. 2.

Create a socket by calling socket. Bind the socket by calling bind. Provide a sockaddr struct that specifies information about the desired port and family. Connect the socket by calling connect (optional). Note that a connected UDP socket is not a connection in the purest sense of the word. However, it provides two advantages over an unconnected socket. First, it removes the need to specify the destination address every time you send a new message. Second, your app may receive errors when a packet cannot be delivered. This error delivery is not guaranteed with UDP, however; it is dependent on network conditions that are beyond your apps control.

3.

From there, you can work with the connection in three ways:

If you are using GCD for run loop integration (recommended), create a dispatch source by calling dispatch_source_create. Assign an event handler to the dispatch source. Optionally assign a cancellation handler. Finally, pass the dispatch source to the dispatch_resume function to begin handling events. If you are using CFSocket for integration, this technique is somewhat more complicated, but makes it easier to interface your code with some Cocoa APIs. However, CFSocket objects use a single object to represent a connection (much like sockets at the POSIX layer), whereas most Cocoa APIs are designed to interface with stream-based APIs that use separate objects for sending and receiving. As a result, some Cocoa APIs that expect read or write streams may be difficult to use in conjunction with CFSocketRef objects. To use CFSocket:
1.

Create an object to use for managing the connection. If you are writing Objective-C code, this can be a class. If you are writing pure C code, this should be a Core Foundation object, such as a mutable dictionary. Create a context object to describe that object.
CFSocketContext ctxt;

2.

ctxt.version = 0; ctxt.info = my_context_object;

2012-12-13 | 2012 Apple Inc. All Rights Reserved.

18

Using Sockets and Socket Streams Working with Packet-Based Sockets

ctxt.retain = CFRetain; ctxt.release = CFRelease; ctxt.copyDescription = NULL;

3.

Create a CFSocket object (CFSocketRef) for the CFSocketNativeHandle object by calling CFSocketCreateWithNative. Be sure to set (at minimum) the kCFSocketDataCallBack flag in your callBackTypes parameter value. Do not set the kCFSocketAcceptCallBack flag. Youll also need to provide a pointer to a CFSocketCallBack callback function as the callout parameter value. For example:
CFSocketRef connection = CFSocketCreateWithNative(kCFAllocatorDefault, sock, kCFSocketDataCallBack, handleNetworkData, &ctxt);

4.

Tell Core Foundation that it is allowed to close the socket when the underlying Core Foundation object is invalidated.
CFOptionFlags sockopt = CFSocketGetSocketFlags(connection);

sockopt |= kCFSocketCloseOnInvalidate | kCFSocketAutomaticallyReenableReadCallBack; CFSocketSetSocketFlags(connection, sockopt);

5.

Create an event source for the socket and schedule it on your run loop.
CFRunLoopSourceRef socketsource = CFSocketCreateRunLoopSource( kCFAllocatorDefault, connection, 0);

CFRunLoopAddSource(CFRunLoopGetCurrent(), socketsource, kCFRunLoopDefaultMode);

2012-12-13 | 2012 Apple Inc. All Rights Reserved.

19

Using Sockets and Socket Streams Obtaining the Native Socket Handle for a Socket Stream

Whenever new data becomes available, the data handler callback gets called. In your callback, if the value of the callbackType parameter is kCFSocketConnectCallBack, check the data parameter passed into the callback. If it is NULL, you have connected to the host. You can then send data using the CFSocketSendData function. When you are finished with the socket, close and invalidate it by calling the CFSocketInvalidate function. At any point, you can also access the underlying BSD socket by calling the CFSocketGetNative function. For more information, see CFSocket Reference . For sample code, see the UDPEcho sample code project.

If you are using pure POSIX sockets, use the select system call to wait for data, then use the read and write system calls to perform I/O. To learn more about sending and receiving UDP packets with the POSIX socket API, read the UNIX Socket FAQ at http://developerweb.net/.

Obtaining the Native Socket Handle for a Socket Stream


Sometimes when working with socket-based streams (NSInputStream, NSOutputStream, CFReadStream, or CFWriteStream), you may need to obtain the underlying socket handle associated with a stream. For example, you might want to find out the IP address and port number for each end of the stream with getsockname and getpeername, or set socket options with setsockopt. To obtain the native socket handle for an input stream, call the following method:
-(int) socknumForNSInputStream: (NSStream *)stream { int sock = -1; NSData *sockObj = [stream propertyForKey: (__bridge NSString *)kCFStreamPropertySocketNativeHandle]; if ([sockObj isKindOfClass:[NSData class]] && ([sockObj length] == sizeof(int)) ) { const int *sockptr = (const int *)[sockObj bytes]; sock = *sockptr; } return sock; }

2012-12-13 | 2012 Apple Inc. All Rights Reserved.

20

Using Sockets and Socket Streams Obtaining the Native Socket Handle for a Socket Stream

You can do the same thing with an output stream, but you only need to do this with one or the other because the input and output streams for a given connection always share the same underlying native socket. Note: If you are working with a Core Foundation stream, you can do the same thing with CFReadStreamCopyProperty, CFDataGetLength, and CFDataGetBytes.

2012-12-13 | 2012 Apple Inc. All Rights Reserved.

21

Resolving DNS Hostnames

This article explains how to resolve a DNS hostname in a way that offers you maximum flexibility. Important: Most high-level OS X and iOS APIs allow you to connect to a host by its DNS name, and, as a rule, you should do so. A hostname can map onto multiple IP addresses simultaneously. If you resolve the hostname yourself, you must choose which IP address to use when connecting to the remote host. By contrast, when you connect by name, you allow the operating system to choose the best way of connecting on your behalf. This is particularly true for Bonjour services because the IP address can change at any time. However, connecting by name is not always possible. If you must use APIs that require an IP address, OS X and iOS provide a number of ways to obtain one or more addresses for a DNS hostname. This appendix describes those techniques. Before you read this appendix, read Avoid Resolving DNS Names Before Connecting to a Host in Networking Overview to learn why you probably should not do the things described below. There are three primary APIs in OS X and iOS for resolving hostnames: NSHost (only in OS X), CFHost, and the POSIX resolver API.

NSHostAlthough passing NSHost is a common way to pass hostnames to other APIs, using NSHost to resolve addresses yourself is generally discouraged because it is a synchronous API. Thus, using it on the main thread can cause serious performance degradation. Because this API is discouraged, its use is not described here. CFHostThe CFHost API allows you to perform resolution asynchronously, and is the preferred way to resolve hostnames if you must resolve them yourself. POSIXThe POSIX layer provides several functions for resolving hostnames. These functions should be used only if you are writing portable code that must be shared with non-Apple platforms or if you are integrating your code with existing POSIX networking code.

Resolving Hostnames with CFHost


To resolve a host with CFHost:
1.

Create a CFHostRef object by calling CFHostCreateWithName.

2012-12-13 | 2012 Apple Inc. All Rights Reserved.

22

Resolving DNS Hostnames Resolving Hostnames with POSIX Calls

2.

Call CFHostSetClient and provide the context object of your choice and a callback function that will be called when resolution completes. Call CFHostScheduleWithRunLoop to schedule the resolver on your run loop. Call CFHostStartInfoResolution to tell the resolver to start resolving, passing kCFHostAddresses as the second parameter to indicate that you want it to return IP addresses. Wait for the resolver to call your callback. Within your callback, obtain the results by calling CFHostGetAddressing. This function returns an array of CFDataRef objects, each of which contains a POSIX sockaddr structure.

3. 4.

5.

The process for reverse name resolution (translating an IP address into a hostname) is similar, except that you call CFHostCreateWithAddress to create the object, pass kCFHostNames to CFHostStartInfoResolution, and call CFHostGetNames to retrieve the results.

Resolving Hostnames with POSIX Calls


If you intend to use POSIX calls to resolve hostnames, be aware that these calls are synchronous and should not be used on the main thread in a GUI app. Instead, you should either create a separate POSIX thread or a GCD task and perform these calls within that context. Important: Using POSIX calls to resolve a hostname on your main program thread in iOS will cause your application to be killed if the resolution time out. POSIX defines three functions in <netdb.h> for resolving hostnames:
getaddrinfo

Returns all the resolved addresses for a given hostname. This function is the preferred way to obtain address information at the POSIX level. You can find sample code in its man page (linked above).
Important: Some older DNS servers do not reply to IPv6 lookup requests with an error. The POSIX getaddrinfo function attempts to hide this misbehavior by canceling outstanding IPv6 queries shortly after receiving a successful IPv4 reply. If your app would benefit from continuing to receive IPv6 addresses until you connect successfully (rather than stopping as soon as you have an IPv4 address that might or might not work), then you should use an asynchronous API such as CFHost. gethostbyname

Returns a single IPv4 address for a given hostname. This function is discouraged for new development because it is limited to IPv4 addresses.

2012-12-13 | 2012 Apple Inc. All Rights Reserved.

23

Resolving DNS Hostnames Resolving Hostnames with POSIX Calls

gethostbyname2

Returns a single address for a given hostname in the specified address family (AF_INET, AF_INET6, and so on). Although this function allows you to get around the IPv4 limitations of gethostbyname, it still limits your ability to try multiple addresses at once and choose the fastest one. Thus, this function is primarily intended as a nearly drop-in replacement for gethostbyname in existing code. The getaddrinfo is preferred for use in new code. For reverse name resolution (translating an IP address into a hostname), POSIX provides getnameinfo and gethostbyaddr. The getnameinfo function is preferred because it is more flexible. To learn more about these functions, read their respective man pages, linked above.

2012-12-13 | 2012 Apple Inc. All Rights Reserved.

24

Overriding TLS Chain Validation Correctly

This article describes how to override the chain validation behavior of network connections secured with Transport Layer Security (TLS). Important: This article assumes you have already read Cryptographic Services in Security Overview or are otherwise familiar with the concepts of certificates, signatures, and chains of trust. Most production software should not override chain validation. However, it is common to do so during development. By using the techniques described here to override chain validation safely, your users will not be left unprotected if you inadvertently ship a version of your software without disabling that debugging code. When a TLS certificate is verified, the operating system verifies its chain of trust. If that chain of trust contains only valid certificates and ends at a known (trusted) anchor certificate, then the certificate is considered valid. If it does not, it is considered invalid. If you are using a commercially signed certificate from a major vendor, the certificate should just work . However, if you are doing something that falls outside the normcreating client certificates for your users, providing service for multiple domains with a single certificate that is not trusted for those domains, using a self-signed certificate, connecting to a host by IP address (where the networking stack cannot determine the servers hostname), and so onyou must take additional steps to convince the operating system to accept the certificate. At a high level, TLS chain validation is performed by a trust object (SecTrustRef). This object contains a number of flags that control what types of validation are performed. As a rule, you should not touch these flags, but you should be aware of their existence. In addition, the trust object contains a policy (SecPolicyRef) that allows you to provide the hostname that should be used when evaluating a TLS certificate. Finally, the trust object contains a list of trusted anchor certificates that your application can modify. This article is split into multiple parts. The first part, Manipulating Trust Objects (page 26), describes common ways to manipulate the trust object to change validation behavior. The remaining sections, Trust Objects and NSURLConnection (page 28) and Trust Objects and NSStream (page 31), show how to integrate those changes with various networking technologies.

2012-12-13 | 2012 Apple Inc. All Rights Reserved.

25

Overriding TLS Chain Validation Correctly Manipulating Trust Objects

Manipulating Trust Objects


The details of manipulating the trust object depend in large part on what youre trying to override. The two most common things to override are the hostname (which must match either the leaf certificates common name or one of the names in its Subject Alternate Name extension) and the set of anchors (which determine a set of trusted certificate authorities). To add a certificate to the list of trusted anchor certificates, you must copy the existing anchor certificates into an array, create a mutable version of that array, add the new anchor certificate to the mutable array, and tell the trust object to use that newly updated array for future evaluation of trust. A simple function to do this is listed in Listing 1.
Listing 1 Adding an anchor to a SecTrustRef object

SecTrustRef addAnchorToTrust(SecTrustRef trust, SecCertificateRef trustedCert) { CFArrayRef oldAnchorArray = NULL;

if (SecTrustCopyAnchorCertificates(&oldAnchorArray) != errSecSuccess) { /* Something went wrong. */ return NULL; } CFMutableArrayRef newAnchorArray = CFArrayCreateMutableCopy( kCFAllocatorDefault, 0, oldAnchorArray); CFRelease(oldAnchorArray);

CFArrayAppendValue(newAnchorArray, trustedCert);

SecTrustSetAnchorCertificates(trust, newAnchorArray);

return trust;

2012-12-13 | 2012 Apple Inc. All Rights Reserved.

26

Overriding TLS Chain Validation Correctly Manipulating Trust Objects

Note: To construct a SecCertificateRef object for trustedCert, first load a DER-encoded certificate into a CFData object, then call SecCertificateCreateWithData. For more flexibility, including loading certificates in other encodings, you can also use SecItemImport function in OS X v10.7 and later.

To override the hostname (to allow a certificate for one specific site to work for another specific site, or to allow a certificate to work when you connected to a host by its IP address), you must replace the policy object that the trust policy uses to determine how to interpret the certificate. To do this, first create a new TLS policy object for the desired hostname. Then create an array containing that policy. Finally, tell the trust object to use that array for future evaluation of trust. Listing 2 shows a function that does this.
Listing 2 Changing the remote hostname for a SecTrustRef object

SecTrustRef changeHostForTrust(SecTrustRef trust) { CFMutableArrayRef newTrustPolicies = CFArrayCreateMutable( kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks);

SecPolicyRef sslPolicy = SecPolicyCreateSSL(true, CFSTR("www.example.com"));

CFArrayAppendValue(newTrustPolicies, sslPolicy);

#ifdef MAC_BACKWARDS_COMPATIBILITY /* This technique works in OS X (v10.5 and later) */

SecTrustSetPolicies(trust, newTrustPolicies); CFRelease(oldTrustPolicies);

return trust; #else /* This technique works in iOS 2 and later, or OS X v10.7 and later */

CFMutableArrayRef certificates = CFArrayCreateMutable( kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks);

2012-12-13 | 2012 Apple Inc. All Rights Reserved.

27

Overriding TLS Chain Validation Correctly Trust Objects and NSURLConnection

/* Copy the certificates from the original trust object */ CFIndex count = SecTrustGetCertificateCount(trust); CFIndex i=0; for (i = 0; i < count; i++) { SecCertificateRef item = SecTrustGetCertificateAtIndex(trust, i); CFArrayAppendValue(certificates, item); }

/* Create a new trust object */ SecTrustRef newtrust = NULL; if (SecTrustCreateWithCertificates(certificates, newTrustPolicies, &newtrust) != errSecSuccess) { /* Probably a good spot to log something. */

return NULL; }

return newtrust; #endif }

Trust Objects and NSURLConnection


To override the chain validation behavior of NSURLConnection, you must override two methods:

connection:canAuthenticateAgainstProtectionSpace:

This method tells NSURLConnection that it knows how to handle authentication of a particular type. When your application decides whether to trust a server certificate, it is considered a form of authenticationyour application authenticating the server.

connection:didReceiveAuthenticationChallenge:

In this method, your code needs to modify the trust policies, keys, or hostnames provided by the server or the client so that the trust policy evaluates successfully. Listing 3 shows an example of these two methods.

2012-12-13 | 2012 Apple Inc. All Rights Reserved.

28

Overriding TLS Chain Validation Correctly Trust Objects and NSURLConnection

Listing 3

Overriding the trust object used by an NSURLConnection object

// If you are building for OS X 10.7 and later or iOS 5 and later, // leave out the first method and use the second method as the // connection:willSendRequestForAuthenticationChallenge: method. // For earlier operating systems, include the first method, and // use the second method as the connection:didReceiveAuthenticationChallenge: // method.

#ifndef NEW_STYLE - (BOOL)connection:(NSURLConnection *)connection canAuthenticateAgainstProtectionSpace:(NSURLProtectionSpace *)protectionSpace { #pragma unused(connection)

NSString *method = [protectionSpace authenticationMethod]; if (method == NSURLAuthenticationMethodServerTrust) { return YES; } return NO; }

-(void)connection:(NSURLConnection *)connection didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge #else -(void)connection:(NSURLConnection *)connection willSendRequestForAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge #endif { NSURLProtectionSpace *protectionSpace = [challenge protectionSpace]; if ([protectionSpace authenticationMethod] == NSURLAuthenticationMethodServerTrust) { SecTrustRef trust = [protectionSpace serverTrust];

/***** Make specific changes to the trust policy here. *****/

/* Re-evaluate the trust policy. */

2012-12-13 | 2012 Apple Inc. All Rights Reserved.

29

Overriding TLS Chain Validation Correctly Trust Objects and NSURLConnection

SecTrustResultType secresult = kSecTrustResultInvalid; if (SecTrustEvaluate(trust, &secresult) != errSecSuccess) { /* Trust evaluation failed. */

[connection cancel];

// Perform other cleanup here, as needed. return; }

switch (secresult) { case kSecTrustResultUnspecified: // The OS trusts this certificate implicitly. case kSecTrustResultProceed: // The user explicitly told the OS to trust it. { NSURLCredential *credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust]; [challenge.sender useCredential:credential forAuthenticationChallenge:challenge]; return; } default: /* It's somebody else's key. Fall through. */ } /* The server sent a key other than the trusted key. */ [connection cancel];

// Perform other cleanup here, as needed. } }

2012-12-13 | 2012 Apple Inc. All Rights Reserved.

30

Overriding TLS Chain Validation Correctly Trust Objects and NSStream

Trust Objects and NSStream


The way you override trust for an NSStream depends on what you are trying to do. If all you need to do is specify a different TLS hostname, you can do this trivially by executing three lines of code before you open the streams:
Listing 4 Overriding the TLS hostname with NSStream
NSDictionary *sslSettings = [NSDictionary dictionaryWithObjectsAndKeys: @"www.gatwood.net", (__bridge id)kCFStreamSSLPeerName, nil]; if (![myInputStream setProperty: sslSettings forKey: (__bridge NSString *)kCFStreamPropertySSLSettings]) { // Handle the error here. }

This changes the streams notion of its hostname so that when the stream object later creates a trust object, it provides the new name. If you need to actually alter the list of trusted anchors, the process is somewhat more complex. As soon as the stream object creates a trust object, it evaluates it. If that trust evaluation fails, the stream is closed before your code has the opportunity to modify the trust object. Thus, to override trust evaluation, you must:

Disable TLS chain validation. Because the stream never evaluates the TLS chain, the evaluation does not fail, and the stream does not close. Perform the chain validation yourself in the streams delegate (after modifying the trust object appropriately).

By the time your stream delegates event handler gets called to indicate that there is space available on the socket, the operating system has already constructed a TLS channel, obtained a certificate chain from the other end of the connection, and created a trust object to evaluate it. At this point, you have an open TLS stream, but you have no idea whether you can trust the host at the other end. By disabling chain validation, it becomes your responsibility to verify that the host at the other end can be trusted. Among other things, this means:

Do not disable hostname checking by creating a non-TLS policy or passing in a NULL pointer for the hostname. If you are intentionally connecting to a host using a hostname other than one of the names listed on its certificate, you should allow the operation only if the certificate you receive from that host is valid for some other domain that you control . Do not implicitly trust self-signed certificates as anchors (kSecTrustOptionImplicitAnchors). Instead, add your own (self-signed) CA certificate to the list of trusted anchors.

2012-12-13 | 2012 Apple Inc. All Rights Reserved.

31

Overriding TLS Chain Validation Correctly Trust Objects and NSStream

Do not arbitrarily disable other security options, such as checking for expired certificates or roots. There are certain situations where doing so might make sense (such as verifying that a document signed back in 2001 was signed by a certificate that was valid back in 2001), but for networking purposes, the default options should generally be left alone.

With those rules in mind, Listing 5 shows how to use custom TLS anchors with NSStream. This listing also uses the function addAnchorToTrust from Listing 1 (page 26).
Listing 5 Using custom TLS anchors with NSStream

/* Code executed after creating the socket: */

[inStream setProperty:NSStreamSocketSecurityLevelNegotiatedSSL forKey:NSStreamSocketSecurityLevelKey];

NSDictionary *sslSettings = [NSDictionary dictionaryWithObjectsAndKeys: (id)kCFBooleanFalse, (id)kCFStreamSSLValidatesCertificateChain, nil];

[inStream setProperty: sslSettings forKey: (__bridge NSString *)kCFStreamPropertySSLSettings];

...

/* Methods in your stream delegate class */

NSString *kAnchorAlreadyAdded = @"AnchorAlreadyAdded";

- (void)stream:(NSStream *)theStream handleEvent:(NSStreamEvent)streamEvent { if (streamEvent == NSStreamEventHasBytesAvailable || streamEvent == NSStreamEventHasSpaceAvailable) { /* Check it. */

2012-12-13 | 2012 Apple Inc. All Rights Reserved.

32

Overriding TLS Chain Validation Correctly Trust Objects and NSStream

NSArray *certs = [theStream propertyForKey: (__bridge NSString *)kCFStreamPropertySSLPeerCertificates]; SecTrustRef trust = (SecTrustRef)[theStream propertyForKey: (__bridge NSString *)kCFStreamPropertySSLPeerTrust];

/* Because you don't want the array of certificates to keep growing, you should add the anchor to the trust list only upon the initial receipt of data (rather than every time). */ NSNumber *alreadyAdded = [theStream propertyForKey: kAnchorAlreadyAdded]; if (!alreadyAdded || ![alreadyAdded boolValue]) { trust = addAnchorToTrust(trust, self.trustedCert); // defined earlier. [theStream setProperty: [NSNumber numberWithBool: YES] forKey: kAnchorAlreadyAdded]; } SecTrustResultType res = kSecTrustResultInvalid;

if (SecTrustEvaluate(trust, &res)) { /* The trust evaluation failed for some reason. This probably means your certificate was broken in some way or your code is otherwise wrong. */

/* Tear down the input stream. */ [theStream removeFromRunLoop: ... forMode: ...]; [theStream setDelegate: nil]; [theStream close];

/* Tear down the output stream. */ ...

return;

if (res != kSecTrustResultProceed && res != kSecTrustResultUnspecified) { /* The host is not trusted. */

2012-12-13 | 2012 Apple Inc. All Rights Reserved.

33

Overriding TLS Chain Validation Correctly Trust Objects and NSStream

/* Tear down the input stream. */ [theStream removeFromRunLoop: ... forMode: ...]; [theStream setDelegate: nil]; [theStream close];

/* Tear down the output stream. */ ...

} else { // Host is trusted. Handle the data callback normally.

} } }

2012-12-13 | 2012 Apple Inc. All Rights Reserved.

34

Document Revision History

This table describes the changes to Networking Programming Topics .

Date 2012-12-13

Notes Corrected the data type used for the length provided to getsockname, fixed missing ampersands in code snippets, and corrected sockaddr_in6 initialization snippet. New document that explains how to perform certain networking tasks that although somewhat common, are beyond the scope of the overview documentation.

2012-07-19

2012-12-13 | 2012 Apple Inc. All Rights Reserved.

35

Apple Inc. 2012 Apple Inc. All rights reserved. No part of this publication may be reproduced, stored in a retrieval system, or transmitted, in any form or by any means, mechanical, electronic, photocopying, recording, or otherwise, without prior written permission of Apple Inc., with the following exceptions: Any person is hereby authorized to store documentation on a single computer for personal use only and to print copies of documentation for personal use provided that the documentation contains Apples copyright notice. No licenses, express or implied, are granted with respect to any of the technology described in this document. Apple retains all intellectual property rights associated with the technology described in this document. This document is intended to assist application developers to develop applications only for Apple-labeled computers. Apple Inc. 1 Infinite Loop Cupertino, CA 95014 408-996-1010 Apple, the Apple logo, Bonjour, Cocoa, Mac, Objective-C, and OS X are trademarks of Apple Inc., registered in the U.S. and other countries. UNIX is a registered trademark of The Open Group. iOS is a trademark or registered trademark of Cisco in the U.S. and other countries and is used under license.
Even though Apple has reviewed this document, APPLE MAKES NO WARRANTY OR REPRESENTATION, EITHER EXPRESS OR IMPLIED, WITH RESPECT TO THIS DOCUMENT, ITS QUALITY, ACCURACY, MERCHANTABILITY, OR FITNESS FOR A PARTICULAR PURPOSE. AS A RESULT, THIS DOCUMENT IS PROVIDED AS IS, AND YOU, THE READER, ARE ASSUMING THE ENTIRE RISK AS TO ITS QUALITY AND ACCURACY. IN NO EVENT WILL APPLE BE LIABLE FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES RESULTING FROM ANY DEFECT OR INACCURACY IN THIS DOCUMENT, even if advised of the possibility of such damages. THE WARRANTY AND REMEDIES SET FORTH ABOVE ARE EXCLUSIVE AND IN LIEU OF ALL OTHERS, ORAL OR WRITTEN, EXPRESS OR IMPLIED. No Apple dealer, agent, or employee is authorized to make any modification, extension, or addition to this warranty. Some states do not allow the exclusion or limitation of implied warranties or liability for incidental or consequential damages, so the above limitation or exclusion may not apply to you. This warranty gives you specific legal rights, and you may also have other rights which vary from state to state.

Вам также может понравиться